{"record":{"id":"ef3fe200d5d1ca60","repo":"kubernetes/kops","slug":"found-vmss-without-ssh-public-keys","errorCode":null,"errorMessage":"found VMSS without SSH public keys","messagePattern":"found VMSS without SSH public keys","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"upup/pkg/fi/cloudup/azuretasks/vmscaleset.go","lineNumber":175,"sourceCode":"\t\t\tif !strings.Contains(*i.ID, \"api\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tloadBalancerID, err = azure.ParseLoadBalancerID(*i.ID)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to parse loadbalancer ID %s\", *i.ID)\n\t\t\t}\n\t\t}\n\t}\n\n\tosProfile := profile.OSProfile\n\tif osProfile.LinuxConfiguration == nil {\n\t\treturn nil, fmt.Errorf(\"found VMSS without Linux config\")\n\t}\n\tif osProfile.LinuxConfiguration.SSH == nil {\n\t\treturn nil, fmt.Errorf(\"found VMSS without SSH config\")\n\t}\n\tif osProfile.LinuxConfiguration.SSH.PublicKeys == nil {\n\t\treturn nil, fmt.Errorf(\"found VMSS without SSH public keys\")\n\t}\n\tsshKeys := osProfile.LinuxConfiguration.SSH.PublicKeys\n\tif len(sshKeys) != 1 {\n\t\treturn nil, fmt.Errorf(\"expecting exactly 1 SSH key for %q, found %d: %+v\", *s.Name, len(sshKeys), sshKeys)\n\t}\n\n\tvar userData []byte\n\tif profile.UserData != nil {\n\t\tuserData, err = base64.StdEncoding.DecodeString(*profile.UserData)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decode user data: %w\", err)\n\t\t}\n\t}\n\n\tvmss := &VMScaleSet{\n\t\tName:      s.Name,\n\t\tLifecycle: s.Lifecycle,\n\t\tResourceGroup: &ResourceGroup{","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/upup/pkg/fi/cloudup/azuretasks/vmscaleset.go#L157-L193","documentation":"Find() expects linuxConfiguration.ssh.publicKeys to be a non-nil list so it can read the node SSH public key (sshKeys[0].KeyData). A nil list means the SSH block exists but carries no keys, so kOps cannot recover the cluster's authorized key and aborts.","triggerScenarios":"Find() on a VMSS where linuxConfiguration.ssh is set but ssh.publicKeys is nil — SSH block present with zero keys, or a fixture that only sets the path.","commonSituations":"Scale sets whose authorized_keys were removed via portal/CLI; externally authored node pools with an empty ssh block; partially populated test fixtures.","solutions":["Restore the SSH key: update the cluster spec's sshPublicKey and run `kops update cluster --yes` / `kops rolling-update cluster`.","If authored externally, add at least one publicKey entry (path + keyData) to linuxConfiguration.ssh.publicKeys.","Verify the key wasn't lost during a key-rotation edit; re-add it before deleting the old VMSS.","Fix fixtures to include a publicKeys array with one key."],"exampleFix":"// before\n\"ssh\": { \"publicKeys\": null }\n// after\n\"ssh\": { \"publicKeys\": [ { \"path\": \"/home/azureuser/.ssh/authorized_keys\", \"keyData\": \"ssh-rsa AAAA...\" } ] }","handlingStrategy":"validation","validationCode":"keys := vmss.Properties.VirtualMachineProfile.OSProfile.LinuxConfiguration.SSH.PublicKeys\nif keys == nil || len(keys) == 0 {\n  return fmt.Errorf(\"VMSS %s has no SSH public keys\", fi.ValueOf(vmss.Name))\n}","typeGuard":"func hasPublicKeys(lc *compute.VirtualMachineScaleSetLinuxConfiguration) bool {\n  return lc != nil && lc.SSH != nil && lc.SSH.PublicKeys != nil && len(lc.SSH.PublicKeys) > 0\n}","tryCatchPattern":null,"preventionTips":["Keep at least one ssh key defined in the cluster spec","Never remove authorized keys directly on the VMSS","Perform key rotation only via kops edit cluster + update + rolling-update","Fix partial SSH fixtures in tests"],"tags":["azure","vmss","ssh","public-key"],"backgroundTag":"vmss-missing-ssh-config","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}