tstest/natlab: add ACME cert vmtest

This adds a fake vnet ACME service, TXT-backed SetDNS support, and a
VM test that fetches a certificate with tailscale cert, serves it with
tailscale serve, and verifies HTTPS from a second node.

This adds coverage motivated by #19915.

Updates #13038

Change-Id: Ie1e53409509337d81c8fbceb63f59f3dfbd48207
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-06-05 08:57:17 -07:00
committed by Brad Fitzpatrick
parent 84ffcd2759
commit 26864f1302
7 changed files with 818 additions and 6 deletions
+55 -2
View File
@@ -4,12 +4,18 @@
package vmtest
import (
"crypto/ed25519"
"crypto/rand"
"encoding/pem"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"github.com/creachadair/mds/shell"
"github.com/kdomanski/iso9660"
"golang.org/x/crypto/ssh"
)
// createCloudInitISO creates a cidata seed ISO for the given cloud VM node.
@@ -18,6 +24,9 @@ import (
// doesn't use netplan-style network-config; DHCP is enabled in rc.conf).
func (e *Env) createCloudInitISO(n *Node) (string, error) {
metaData := fmt.Sprintf("instance-id: %s\nlocal-hostname: %s\n", n.name, n.name)
if err := ensureDebugSSHKey(); err != nil {
return "", err
}
userData := e.generateUserData(n)
files := map[string]string{
@@ -124,7 +133,7 @@ func (e *Env) generateLinuxUserData(n *Node) string {
// features like Taildrop (which needs a place to stash incoming files)
// have a directory to work with.
ud.WriteString(" - [\"mkdir\", \"-p\", \"/var/lib/tailscale\"]\n")
ud.WriteString(" - [\"/bin/sh\", \"-c\", \"/usr/local/bin/tailscaled --state=mem: --statedir=/var/lib/tailscale &\"]\n")
fmt.Fprintf(&ud, " - [\"/bin/sh\", \"-c\", \"%s/usr/local/bin/tailscaled --state=mem: --statedir=/var/lib/tailscale &\"]\n", tailscaledEnvPrefix(n))
ud.WriteString(" - [\"sleep\", \"2\"]\n")
// Start tta (Tailscale Test Agent).
@@ -183,7 +192,7 @@ func (e *Env) generateFreeBSDUserData(n *Node) string {
// path). --statedir provides a VarRoot so features like Taildrop have a
// directory.
ud.WriteString(" - \"mkdir -p /var/lib/tailscale\"\n")
ud.WriteString(" - \"export PATH=/usr/local/bin:$PATH && /usr/local/bin/tailscaled --state=mem: --statedir=/var/lib/tailscale </dev/null >/var/log/tailscaled.log 2>&1 &\"\n")
fmt.Fprintf(&ud, " - \"export PATH=/usr/local/bin:$PATH && %s/usr/local/bin/tailscaled --state=mem: --statedir=/var/lib/tailscale </dev/null >/var/log/tailscaled.log 2>&1 &\"\n", tailscaledEnvPrefix(n))
ud.WriteString(" - \"sleep 2\"\n")
// Start tta (Tailscale Test Agent), with the same stdio redirection.
@@ -191,3 +200,47 @@ func (e *Env) generateFreeBSDUserData(n *Node) string {
return ud.String()
}
func tailscaledEnvPrefix(n *Node) string {
env := n.vnetNode.Env()
if len(env) == 0 {
return ""
}
parts := make([]string, 0, len(env))
for _, e := range env {
parts = append(parts, e.Key+"="+shell.Quote(e.Value))
}
sort.Strings(parts)
return strings.Join(parts, " ") + " "
}
func ensureDebugSSHKey() error {
const keyPath = "/tmp/vmtest_key"
if privPEM, err := os.ReadFile(keyPath); err == nil {
if _, err := os.Stat(keyPath + ".pub"); err == nil {
return nil
}
signer, err := ssh.ParsePrivateKey(privPEM)
if err != nil {
return fmt.Errorf("parse %s: %w", keyPath, err)
}
return os.WriteFile(keyPath+".pub", ssh.MarshalAuthorizedKey(signer.PublicKey()), 0644)
}
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return err
}
block, err := ssh.MarshalPrivateKey(priv, "vmtest")
if err != nil {
return err
}
if err := os.WriteFile(keyPath, pem.EncodeToMemory(block), 0600); err != nil {
return err
}
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
return err
}
return os.WriteFile(keyPath+".pub", ssh.MarshalAuthorizedKey(sshPub), 0644)
}