Skip to content

feat(instance): add fetch-keys to ssh commands #4622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 2, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
🟥🟥🟥 STDERR️️ 🟥🟥🟥️
Keys registered via the Scaleway Console will be propagated to the selected servers.
The command 'ssh <server-ip> -t -l <username> scw-fetch-ssh-keys --upgrade' will be run on the servers matching the zone and project filters.
Keep in mind that you need to be able to connect to your server with another key than the one you want to add.
Keep in mind that SSH keys are scoped by project.

USAGE:
scw instance ssh fetch-keys [arg=value ...]

ARGS:
[project-id] Fetch the keys on all servers in the given Project
[username=root] Username used for the SSH connection
[zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | fr-par-2 | fr-par-3 | nl-ams-1 | nl-ams-2 | nl-ams-3 | pl-waw-1 | pl-waw-2 | pl-waw-3)

FLAGS:
-h, --help help for fetch-keys

GLOBAL FLAGS:
-c, --config string The path to the config file
-D, --debug Enable debug mode
-o, --output string Output format: json or human, see 'scw help output' for more info (default "human")
-p, --profile string The config profile to use
--web open console page for the current ressource
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ USAGE:
scw instance ssh list-keys <server-id ...> [arg=value ...]

ARGS:
server-id Server to add your key to
server-id Server which keys are to be listed
[zone=fr-par-1] Zone to target. If none is passed will use default zone from the config (fr-par-1 | fr-par-2 | fr-par-3 | nl-ams-1 | nl-ams-2 | nl-ams-3 | pl-waw-1 | pl-waw-2 | pl-waw-3)

FLAGS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ USAGE:

UTILITY COMMANDS:
add-key Add a public key to a server
fetch-keys Fetch SSH keys from the console and install them on multiple servers
list-keys List manually added public keys
remove-key Remove a manually added public key from a server

Expand Down
27 changes: 26 additions & 1 deletion docs/commands/instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ This API allows you to manage your Instances.
- [Wait for snapshot to reach a stable state](#wait-for-snapshot-to-reach-a-stable-state)
- [SSH Utilities](#ssh-utilities)
- [Add a public key to a server](#add-a-public-key-to-a-server)
- [Fetch SSH keys from the console and install them on multiple servers](#fetch-ssh-keys-from-the-console-and-install-them-on-multiple-servers)
- [Install a ssh config with all your servers as host
It generate hosts for instance servers, baremetal, apple-silicon and bastions](#install-a-ssh-config-with-all-your-servers-as-host
it-generate-hosts-for-instance-servers,-baremetal,-apple-silicon-and-bastions)
Expand Down Expand Up @@ -2769,6 +2770,30 @@ scw instance ssh add-key [arg=value ...]



### Fetch SSH keys from the console and install them on multiple servers

Keys registered via the Scaleway Console will be propagated to the selected servers.
The command 'ssh <server-ip> -t -l <username> scw-fetch-ssh-keys --upgrade' will be run on the servers matching the zone and project filters.
Keep in mind that you need to be able to connect to your server with another key than the one you want to add.
Keep in mind that SSH keys are scoped by project.

**Usage:**

```
scw instance ssh fetch-keys [arg=value ...]
```


**Args:**

| Name | | Description |
|------|---|-------------|
| project-id | | Fetch the keys on all servers in the given Project |
| username | Default: `root` | Username used for the SSH connection |
| zone | Default: `fr-par-1`<br />One of: `fr-par-1`, `fr-par-2`, `fr-par-3`, `nl-ams-1`, `nl-ams-2`, `nl-ams-3`, `pl-waw-1`, `pl-waw-2`, `pl-waw-3` | Zone to target. If none is passed will use default zone from the config |



### Install a ssh config with all your servers as host
It generate hosts for instance servers, baremetal, apple-silicon and bastions

Expand Down Expand Up @@ -2807,7 +2832,7 @@ scw instance ssh list-keys <server-id ...> [arg=value ...]

| Name | | Description |
|------|---|-------------|
| server-id | Required | Server to add your key to |
| server-id | Required | Server which keys are to be listed |
| zone | Default: `fr-par-1`<br />One of: `fr-par-1`, `fr-par-2`, `fr-par-3`, `nl-ams-1`, `nl-ams-2`, `nl-ams-3`, `pl-waw-1`, `pl-waw-2`, `pl-waw-3` | Zone to target. If none is passed will use default zone from the config |


Expand Down
1 change: 1 addition & 0 deletions internal/namespaces/instance/v1/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func GetCommands() *core.Commands {
instanceSSH(),
sshAddKeyCommand(),
sshConfigInstallCommand(),
sshFetchKeysCommand(),
sshListKeysCommand(),
sshRemoveKeyCommand(),
instanceServerGetRdpPassword(),
Expand Down
101 changes: 100 additions & 1 deletion internal/namespaces/instance/v1/custom_ssh_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"errors"
"fmt"
"hash/crc32"
"os/exec"
"reflect"
"strings"

"github.com/scaleway/scaleway-cli/v2/core"
"github.com/scaleway/scaleway-cli/v2/core/human"
"github.com/scaleway/scaleway-cli/v2/internal/interactive"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
Expand Down Expand Up @@ -132,6 +134,103 @@ Lookup /root/.ssh/authorized_keys on your server for more information`,
}
}

type sshFetchKeysRequest struct {
Zone scw.Zone
ProjectID string
Username string
}

func sshFetchKeysCommand() *core.Command {
return &core.Command{
Namespace: "instance",
Resource: "ssh",
Verb: "fetch-keys",
Groups: []string{"utility"},
Short: "Fetch SSH keys from the console and install them on multiple servers",
Long: `Keys registered via the Scaleway Console will be propagated to the selected servers.
The command 'ssh <server-ip> -t -l <username> scw-fetch-ssh-keys --upgrade' will be run on the servers matching the zone and project filters.
Keep in mind that you need to be able to connect to your server with another key than the one you want to add.
Keep in mind that SSH keys are scoped by project.`,
ArgsType: reflect.TypeOf(sshFetchKeysRequest{}),
ArgSpecs: core.ArgSpecs{
{
Name: "project-id",
Short: "Fetch the keys on all servers in the given Project",
Required: false,
},
{
Name: "username",
Short: "Username used for the SSH connection",
Default: core.DefaultValueSetter("root"),
},
core.ZoneArgSpec(((*instance.API)(nil)).Zones()...),
},
Run: func(ctx context.Context, argsI interface{}) (interface{}, error) {
args := argsI.(*sshFetchKeysRequest)
api := instance.NewAPI(core.ExtractClient(ctx))

listServersRequest := &instance.ListServersRequest{
Zone: args.Zone,
}
if args.ProjectID != "" {
listServersRequest.Project = &args.ProjectID
}
servers, err := api.ListServers(listServersRequest, scw.WithAllPages(), scw.WithContext(ctx))
if err != nil {
return nil, fmt.Errorf("failed to fetch servers: %w", err)
}

for i, server := range servers.Servers {
msg := fmt.Sprintf("Loading SSH keys on server %q", server.Name)
if i == 0 {
_, _ = interactive.Println(">", msg)
} else {
_, _ = interactive.Println("\n>", msg)
}

if server.State != instance.ServerStateRunning {
_, _ = interactive.Printf("Failed: server %q is not running", server.Name)

continue
}

if len(server.PublicIPs) == 0 {
_, _ = interactive.Printf("Failed: server %q has no public IP", server.Name)

continue
}

for _, publicIP := range server.PublicIPs {
sshArgs := []string{
publicIP.Address.String(),
"-t",
"-l", args.Username,
"/usr/sbin/scw-fetch-ssh-keys",
"--upgrade",
}

sshCmd := exec.Command("ssh", sshArgs...)

_, _ = interactive.Println(sshCmd)

exitCode, err := core.ExecCmd(ctx, sshCmd)
if err != nil || exitCode != 0 {
if err != nil {
_, _ = interactive.Println("Failed:", err)
} else {
_, _ = interactive.Println("Failed: ssh command failed with exit code", exitCode)
}
} else {
_, _ = interactive.Println("Success")
}
}
}

return &core.SuccessResult{Empty: true}, nil
},
}
}

type sshListKeysRequest struct {
Zone scw.Zone
ServerID string
Expand All @@ -151,7 +250,7 @@ Lookup /root/.ssh/authorized_keys on your server for more information`,
ArgSpecs: core.ArgSpecs{
{
Name: "server-id",
Short: "Server to add your key to",
Short: "Server which keys are to be listed",
Positional: true,
Required: true,
},
Expand Down
Loading