Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 53 additions & 19 deletions cmd/setec/setec.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/tailscale/setec/client/setec"
"github.com/tailscale/setec/internal/tinktestutil"
"github.com/tailscale/setec/server"
"github.com/tailscale/setec/tpmkey"
"github.com/tailscale/setec/types/api"
"github.com/tink-crypto/tink-go-awskms/v2/integration/awskms"
"github.com/tink-crypto/tink-go/v2/tink"
Expand Down Expand Up @@ -65,7 +66,17 @@ the node on the tailnet.
With the --dev flag, the server runs with a dummy KMS. This mode is intended
for debugging and is NOT SAFE for production use.

Otherwise you must provide a --kms-key-name to use to encrypt the database.
Otherwise you must provide a key to encrypt the database, using exactly one of:

--kms-key-name - to use a key stored in AWS KMS.

--tpm-key-file - to use a key sealed to a TPM 2.0 device, stored in the named
file. If the file does not exist, a new key is generated,
sealed to the TPM, and saved there. The sealed key can only
be used by the TPM that created it.

Use the --tpm-device flag to select a TPM device; the
default is /dev/tpmrm0

Most of the settings can be set via environment variables as well as flags.

Expand All @@ -74,7 +85,9 @@ Most of the settings can be set via environment variables as well as flags.
-------------------------------------------------------------------------
--state-dir SETEC_DIR path (required)
--hostname SETEC_HOSTNAME string (required)
--kms-key-name SETEC_KMS_KEY_NAME string (required unless --dev)
--kms-key-name SETEC_KMS_KEY_NAME string (see above)
--tpm-key-file SETEC_TPM_KEY_FILE path (see above)
--tpm-device SETEC_TPM_DEVICE path (/dev/tpmrm0)
--backup-bucket SETEC_BACKUP_BUCKET string (optional)
--backup-bucket-region SETEC_BACKUP_BUCKET_REGION string (optional)
--backup-role SETEC_BACKUP_ROLE string (optional)
Expand Down Expand Up @@ -164,6 +177,8 @@ var serverArgs struct {
StateDir string `flag:"state-dir,default=$SETEC_STATE_DIR,Server state directory"`
Hostname string `flag:"hostname,default=$SETEC_HOSTNAME,Tailscale hostname to use"`
KMSKeyName string `flag:"kms-key-name,default=$SETEC_KMS_KEY_NAME,Name of KMS key to use for database encryption"`
TPMKeyFile string `flag:"tpm-key-file,default=$SETEC_TPM_KEY_FILE,Path of TPM-sealed key file to use for database encryption (created if missing)"`
TPMDevice string `flag:"tpm-device,default=$SETEC_TPM_DEVICE,Path of TPM device to use with --tpm-key-file"`
BackupBucket string `flag:"backup-bucket,default=$SETEC_BACKUP_BUCKET,Name of AWS S3 bucket to use for database backups"`
BackupBucketRegion string `flag:"backup-bucket-region,default=$SETEC_BACKUP_BUCKET_REGION,AWS region of the backup S3 bucket"`
BackupRole string `flag:"backup-role,default=$SETEC_BACKUP_ROLE,Name of AWS IAM role to assume to write backups"`
Expand All @@ -188,14 +203,15 @@ func runServer(env *command.Env) error {
if serverArgs.Hostname == "" {
serverArgs.Hostname = "setec-dev"
}
if serverArgs.KMSKeyName == "" {

log.Printf("dev mode: state dir is %q", serverArgs.StateDir)
log.Printf("dev mode: hostname is %q", serverArgs.Hostname)
if serverArgs.KMSKeyName == "" && serverArgs.TPMKeyFile == "" {
log.Println("dev mode: using dummy KMS, NOT SAFE FOR PRODUCTION USE")
kek = &tinktestutil.DummyAEAD{
Name: "SetecDevOnlyDummyEncryption",
}
}
log.Printf("dev mode: state dir is %q", serverArgs.StateDir)
log.Printf("dev mode: hostname is %q", serverArgs.Hostname)
log.Println("dev mode: using dummy KMS, NOT SAFE FOR PRODUCTION USE")
}

if serverArgs.StateDir == "" {
Expand All @@ -205,19 +221,37 @@ func runServer(env *command.Env) error {
return errors.New("--hostname must be specified")
}
if kek == nil {
if serverArgs.KMSKeyName == "" {
return errors.New("--kms-key-name must be specified")
}
// Tink requires prefixing the key identifier with a URI
// scheme that identifies the correct backend to use.
uri := "aws-kms://" + serverArgs.KMSKeyName
kmsClient, err := awskms.NewClientWithOptions(uri)
if err != nil {
return fmt.Errorf("creating AWS KMS client: %v", err)
}
kek, err = kmsClient.GetAEAD(uri)
if err != nil {
return fmt.Errorf("getting KMS key handle: %v", err)
switch {
case serverArgs.KMSKeyName != "" && serverArgs.TPMKeyFile != "":
return errors.New("--kms-key-name and --tpm-key-file are mutually exclusive")
case serverArgs.KMSKeyName != "":
// Tink requires prefixing the key identifier with a URI
// scheme that identifies the correct backend to use.
uri := "aws-kms://" + serverArgs.KMSKeyName
kmsClient, err := awskms.NewClientWithOptions(uri)
if err != nil {
return fmt.Errorf("creating AWS KMS client: %v", err)
}
kek, err = kmsClient.GetAEAD(uri)
if err != nil {
return fmt.Errorf("getting KMS key handle: %v", err)
}
case serverArgs.TPMKeyFile != "":
devPath := serverArgs.TPMDevice
if devPath == "" {
devPath = tpmkey.DefaultDevice
}
device, err := tpmkey.OpenDevicePath(devPath)
if err != nil {
return fmt.Errorf("opening TPM device %q: %v", devPath, err)
}

kek, err = tpmkey.OpenOrCreate(device, serverArgs.TPMKeyFile)
if err != nil {
return fmt.Errorf("opening TPM-sealed key file: %v", err)
}
default:
return errors.New("either --kms-key-name or --tpm-key-file must be specified")
}
}

Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ the server maintains secrets in encrypted storage, keeps an audit log of
accesses, and manages periodic backups.

The setec server integrates with existing key-management infrastructure to
bootstrap its own deployment (as of 24-Sep-2023, AWS KMS is supported).
bootstrap its own deployment (as of 24-Sep-2023, AWS KMS is supported; as of
19-Jul-2026, a key sealed to a TPM device can be used).
Once the server is running on a tailnet, other programs can use it to access
their production secrets with a basic WireGuard-encrypted HTTP request, rather
than having to distribute secrets via files, environment variables, or manual
Expand Down
41 changes: 38 additions & 3 deletions docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,36 @@ The server stores secrets in an encrypted file in the state directory. When the
server starts, it requires an **access key** to unlock the database.

In production, the server fetches an access key from an AWS KMS secret, whose
ARN is specified via the `--kms-key-name` flag. As of 05-May-2024, AWS KMS is
the only supported production access key store; we may add others in the
future.
ARN is specified via the `--kms-key-name` flag.

This mode also requires access to the AWS APIs: If you are running the server
in AWS (e.g., an EC2 VM), you would typically grant access to the key via an
IAM role on the VM. Alternatively, you can plumb in credentials via environment
variables, for example using [`aws-vault`][awsvault] or similar.

For environments where no KMS is available (e.g., a homelab), the server can
instead use a key sealed to a local TPM 2.0 device, via the `--tpm-key-file`
flag. The flag names a file in which the server stores a TPM-sealed key blob;
if the file does not exist, the server generates a new key, seals it to the
TPM, and saves it there on first startup. The sealed blob can only be unsealed
by the TPM that created it, so a copy of the database and the key file together
cannot be decrypted elsewhere. By default the server uses the TPM at
`/dev/tpmrm0`; use `--tpm-device` to select a different device.

This also works with virtual TPMs, such as the vTPM QEMU (and thus Proxmox) can
attach to a VM. Two caveats to be aware of when using a vTPM:

- A vTPM's state is stored by the host (for Proxmox, in the VM's "TPM State"
disk), so an attacker who obtains that state along with the database can
still recover the key. Snapshots or backups of the whole VM including its
TPM state likewise contain everything needed to decrypt the database.
- The key is bound to that vTPM instance: if the VM is rebuilt without
preserving its TPM state, the database becomes unrecoverable. Keep a
separate backup of the database key if you cannot afford that risk.

On systems without TPM support (such as Darwin), the server will report an
error at startup when `--tpm-key-file` is set.

For development and testing purposes, the server also supports a `--dev` flag,
which runs using a "dummy" static access key. **This mode is not secure for
production use**, but is useful for testing and debugging integrations locally.
Expand Down Expand Up @@ -91,6 +112,20 @@ production use**, but is useful for testing and debugging integrations locally.
--kms-key-name=arn:aws:kms:us-east-1:123456789012:key/b8074b63-13c0-4345-a9d8-e236267d2af1
```

3. To run a self-hosted setec server using a key sealed to the local TPM:

```shell
TS_AUTHKEY=tskey-auth-kf4k3k3y4testCNTRL-ZmFrZSBrZXkgZm9yIHRlc3Q setec server \
--hostname=secrets \
--state-dir=$HOME/setec-state \
--tpm-key-file=$HOME/setec-state/setec-tpm.key
```

The sealed key file is created automatically the first time the server
starts. The server must be able to read the TPM device (`/dev/tpmrm0` by
default): either run it as root, or add its user to the group owning the
device.

Once you have run the server, you can grant access to it via your [tailnet
ACL][acl]. For example, if we assume your server's Tailscale address is
100.64.5.6, the following [ACL grants][grant] would give the administrators of
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/creachadair/mds v0.25.15
github.com/creachadair/msync v0.8.1
github.com/google/go-cmp v0.7.0
github.com/google/go-tpm v0.9.8
github.com/tink-crypto/tink-go-awskms/v2 v2.1.0
github.com/tink-crypto/tink-go/v2 v2.6.0
golang.org/x/term v0.38.0
Expand Down Expand Up @@ -47,6 +48,7 @@ require (
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-tpm-tools v0.4.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
Expand Down Expand Up @@ -81,6 +83,6 @@ require (
golang.org/x/tools v0.39.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 // indirect
)
24 changes: 20 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/GoogleCloudPlatform/confidential-space/server v0.0.0-20260522213940-e5c6d01a3007 h1:DoeEFwEGBdqcawmpiWtSsSVVZ+wk3zpqvcvssO2JLmY=
github.com/GoogleCloudPlatform/confidential-space/server v0.0.0-20260522213940-e5c6d01a3007/go.mod h1:s8F0JYEods/WL03WxZaGsWCnumZeeLD+WKHzspOV9u0=
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
Expand Down Expand Up @@ -101,8 +103,20 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I=
github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc h1:SG12DWUUM5igxm+//YX5Yq4vhdoRnOG9HkCodkOn+YU=
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
github.com/google/go-eventlog v0.0.3-0.20260416001248-6807b85eecf0 h1:STyioPkz8nqMMIk3+YlyJ/WyEJZxho1YUZXu99uAbQ0=
github.com/google/go-eventlog v0.0.3-0.20260416001248-6807b85eecf0/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ=
github.com/google/go-sev-guest v0.14.0 h1:dCb4F3YrHTtrDX3cYIPTifEDz7XagZmXQioxRBW4wOo=
github.com/google/go-sev-guest v0.14.0/go.mod h1:SK9vW+uyfuzYdVN0m8BShL3OQCtXZe/JPF7ZkpD3760=
github.com/google/go-tdx-guest v0.3.2-0.20250814004405-ffb0869e6f4d h1:Ff8goEP/ue2/rZT5qyoRicuySCYDbAXEZS8Cf1fgsUo=
github.com/google/go-tdx-guest v0.3.2-0.20250814004405-ffb0869e6f4d/go.mod h1:uHy3VaNXNXhl0fiPxKqTxieeouqQmW6A0EfLcaeCYBk=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.9 h1:jZEhnE4WRFbomSssBH2gWaIViIHU1gjH1jz76+xC9bI=
github.com/google/go-tpm-tools v0.4.9/go.mod h1:Omb8zosA8qY9URn1gsrO2i4b6DFqGp29BqNx18V66c4=
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
Expand Down Expand Up @@ -203,6 +217,8 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
Expand Down Expand Up @@ -242,8 +258,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
Expand Down
15 changes: 15 additions & 0 deletions tpmkey/open_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

//go:build linux

package tpmkey

import (
"github.com/google/go-tpm/tpm2/transport"
"github.com/google/go-tpm/tpm2/transport/linuxtpm"
)

func openDevice(path string) (transport.TPMCloser, error) {
return linuxtpm.Open(path)
}
17 changes: 17 additions & 0 deletions tpmkey/open_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

//go:build !linux

package tpmkey

import (
"fmt"
"runtime"

"github.com/google/go-tpm/tpm2/transport"
)

func openDevice(path string) (transport.TPMCloser, error) {
return nil, fmt.Errorf("tpmkey: unsupported OS: %s", runtime.GOOS)
}
Loading
Loading