cmd/tailscaled,ipn/{ipnlocal,store/kubestore}: don't create attestation keys for stores that are not bound to a node (#18322)
Ensure that hardware attestation keys are not added to tailscaled state stores that are Kubernetes Secrets or AWS SSM as those Tailscale devices should be able to be recreated on different nodes, for example, when moving Pods between nodes. Updates tailscale/tailscale#18302 Signed-off-by: Irbe Krumina <irbekrm@gmail.com>
This commit is contained in:
@@ -20,6 +20,90 @@ import (
|
||||
"tailscale.com/kube/kubetypes"
|
||||
)
|
||||
|
||||
func TestKubernetesPodMigrationWithTPMAttestationKey(t *testing.T) {
|
||||
stateWithAttestationKey := `{
|
||||
"Config": {
|
||||
"NodeID": "nSTABLE123456",
|
||||
"AttestationKey": {
|
||||
"tpmPrivate": "c2Vuc2l0aXZlLXRwbS1kYXRhLXRoYXQtb25seS13b3Jrcy1vbi1vcmlnaW5hbC1ub2Rl",
|
||||
"tpmPublic": "cHVibGljLXRwbS1kYXRhLWZvci1hdHRlc3RhdGlvbi1rZXk="
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
secretData := map[string][]byte{
|
||||
"profile-abc123": []byte(stateWithAttestationKey),
|
||||
"_current-profile": []byte("profile-abc123"),
|
||||
}
|
||||
|
||||
client := &kubeclient.FakeClient{
|
||||
GetSecretImpl: func(ctx context.Context, name string) (*kubeapi.Secret, error) {
|
||||
return &kubeapi.Secret{Data: secretData}, nil
|
||||
},
|
||||
CheckSecretPermissionsImpl: func(ctx context.Context, name string) (bool, bool, error) {
|
||||
return true, true, nil
|
||||
},
|
||||
JSONPatchResourceImpl: func(ctx context.Context, name, resourceType string, patches []kubeclient.JSONPatch) error {
|
||||
for _, p := range patches {
|
||||
if p.Op == "add" && p.Path == "/data" {
|
||||
secretData = p.Value.(map[string][]byte)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
store := &Store{
|
||||
client: client,
|
||||
canPatch: true,
|
||||
secretName: "ts-state",
|
||||
memory: mem.Store{},
|
||||
logf: t.Logf,
|
||||
}
|
||||
|
||||
if err := store.loadState(); err != nil {
|
||||
t.Fatalf("loadState failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify we can read the state from the store
|
||||
stateBytes, err := store.ReadState("profile-abc123")
|
||||
if err != nil {
|
||||
t.Fatalf("ReadState failed: %v", err)
|
||||
}
|
||||
|
||||
// The state should be readable as JSON
|
||||
var state map[string]json.RawMessage
|
||||
if err := json.Unmarshal(stateBytes, &state); err != nil {
|
||||
t.Fatalf("failed to unmarshal state: %v", err)
|
||||
}
|
||||
|
||||
// Verify the Config field exists
|
||||
configRaw, ok := state["Config"]
|
||||
if !ok {
|
||||
t.Fatal("Config field not found in state")
|
||||
}
|
||||
|
||||
// Parse the Config to verify fields are preserved
|
||||
var config map[string]json.RawMessage
|
||||
if err := json.Unmarshal(configRaw, &config); err != nil {
|
||||
t.Fatalf("failed to unmarshal Config: %v", err)
|
||||
}
|
||||
|
||||
// The AttestationKey should be stripped by the kubestore
|
||||
if _, hasAttestation := config["AttestationKey"]; hasAttestation {
|
||||
t.Error("AttestationKey should be stripped from state loaded by kubestore")
|
||||
}
|
||||
|
||||
// Verify other fields are preserved
|
||||
var nodeID string
|
||||
if err := json.Unmarshal(config["NodeID"], &nodeID); err != nil {
|
||||
t.Fatalf("failed to unmarshal NodeID: %v", err)
|
||||
}
|
||||
if nodeID != "nSTABLE123456" {
|
||||
t.Errorf("NodeID mismatch: got %q, want %q", nodeID, "nSTABLE123456")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteState(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user