{"record":{"id":"a518c28b778ad305","repo":"kubernetes/kops","slug":"expecting-exactly-1-ssh-key-for-q-found-d-v","errorCode":null,"errorMessage":"expecting exactly 1 SSH key for %q, found %d: %+v","messagePattern":"expecting exactly 1 SSH key for %q, found (.+?): %\\+v","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"upup/pkg/fi/cloudup/azuretasks/vmscaleset.go","lineNumber":179,"sourceCode":"\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{\n\t\t\tName: s.ResourceGroup.Name,\n\t\t},\n\t\tVirtualNetwork: &VirtualNetwork{\n\t\t\tName: to.Ptr(subnetID.VirtualNetworkName),","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/upup/pkg/fi/cloudup/azuretasks/vmscaleset.go#L161-L197","documentation":"kOps models exactly one SSH public key per VMSS (the cluster's sshPublicKey). Find() enforces len(publicKeys)==1; if Azure reports 0 or multiple keys on the scale set, reconciliation cannot map them back to the single kops-managed key and this error names the scale set plus the keys found.","triggerScenarios":"Find() on a VMSS whose linuxConfiguration.ssh.publicKeys has length != 1 — typically 2+ keys after someone appended extra keys manually, or 0 handled by the preceding nil/empty cases.","commonSituations":"Manual key additions to nodes via portal/CLI; Bastion or JIT tools injecting extra keys; key rotation done directly on the scale set instead of through kops; multi-key cluster specs edited by hand.","solutions":["Remove extra keys so exactly one remains, or better: set the desired key in the cluster spec (`kops edit cluster` sshPublicKey / `kops replace`) and run `kops update cluster --yes` then `kops rolling-update cluster`.","For temporary access, use Azure Bastion/just-in-time access instead of adding VMSS-level keys.","If a rotation left both old and new keys, roll to the new key and delete the old VMSS instances.","Align test fixtures to a single key."],"exampleFix":"// before\n\"publicKeys\": [ {\"keyData\": \"ssh-rsa OLD\"}, {\"keyData\": \"ssh-rsa NEW\"} ]\n// after\n\"publicKeys\": [ {\"keyData\": \"ssh-rsa NEW\"} ]","handlingStrategy":"validation","validationCode":"keys := vmss.Properties.VirtualMachineProfile.OSProfile.LinuxConfiguration.SSH.PublicKeys\nif len(keys) != 1 {\n  return fmt.Errorf(\"VMSS %s must have exactly 1 SSH key, found %d\", fi.ValueOf(vmss.Name), len(keys))\n}","typeGuard":"func hasExactlyOneKey(lc *compute.VirtualMachineScaleSetLinuxConfiguration) bool {\n  return lc != nil && lc.SSH != nil && len(lc.SSH.PublicKeys) == 1\n}","tryCatchPattern":null,"preventionTips":["Use Bastion/JIT for ad-hoc access instead of adding VMSS keys","Rotate keys only through kops cluster spec","Audit VMSS osProfile for drift before kops operations","Keep test fixtures to a single key"],"tags":["azure","vmss","ssh","key-count"],"backgroundTag":"vmss-ssh-key-mismatch","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}