Skip to content
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

Export ssh public keys under ssh-pub-keys in topo json #2387

Merged
merged 2 commits into from
Jan 11, 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
12 changes: 8 additions & 4 deletions clab/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import (

log "github.com/sirupsen/logrus"
"github.com/srl-labs/containerlab/types"
"github.com/srl-labs/containerlab/utils"
)

// GenerateExports generates various export files and writes it to a lab location.
// GenerateExports generates various export files and writes it to a file in the lab directory.
func (c *CLab) GenerateExports(ctx context.Context, f io.Writer, p string) error {
err := c.exportTopologyDataWithTemplate(ctx, f, p)
if err != nil {
Expand All @@ -36,9 +37,11 @@ func (c *CLab) GenerateExports(ctx context.Context, f io.Writer, p string) error
// TopologyExport holds a combination of CLab structure and map of NodeConfig types,
// which expands Node definitions with dynamically created values.
type TopologyExport struct {
Name string `json:"name"`
Type string `json:"type"`
Clab *CLab `json:"clab,omitempty"`
Name string `json:"name"`
Type string `json:"type"`
Clab *CLab `json:"clab,omitempty"`
// SSHPubKeys is a list of string representations of SSH public keys.
SSHPubKeys []string `json:"SSHPubKeys,omitempty"`
NodeConfigs map[string]*types.NodeConfig `json:"nodeconfigs,omitempty"`
}

Expand Down Expand Up @@ -87,6 +90,7 @@ func (c *CLab) exportTopologyDataWithTemplate(_ context.Context, w io.Writer, p
Name: c.Config.Name,
Type: "clab",
Clab: c,
SSHPubKeys: utils.MarshalSSHPubKeys(c.SSHPubKeys),
NodeConfigs: make(map[string]*types.NodeConfig),
}

Expand Down
1 change: 1 addition & 0 deletions clab/export_templates/auto.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"mgmt": {{ ToJSONPretty .Clab.Config.Mgmt " " " "}}
}
},
"ssh-pub-keys": {{ ToJSON .SSHPubKeys }},
"nodes": { {{- $i:=0 }}{{range $n, $c := .NodeConfigs}}{{if $i}},{{end}}
"{{$n}}": {
"index": "{{$c.Index}}",
Expand Down
1 change: 1 addition & 0 deletions clab/export_templates/full.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "{{ .Name }}",
"type": "{{ .Type }}",
"clab": {{ ToJSONPretty .Clab " " " "}},
"ssh-pub-keys": {{ ToJSON .SSHPubKeys }},
"nodes": { {{- $i:=0 }}{{range $n, $c := .NodeConfigs}}{{if $i}},{{end}}{{ $k := dict "tls-key" $c.TLSKey }}
"{{$n}}":{{ $cj := $c | data.ToJSON | data.JSON }} {{ $dst := coll.Merge $cj $k }}{{ ToJSONPretty $dst " " " " }}{{$i = add $i 1}}{{end}}
},
Expand Down
32 changes: 0 additions & 32 deletions nodes/srl/sshkey.go

This file was deleted.

45 changes: 0 additions & 45 deletions nodes/srl/sshkey_test.go

This file was deleted.

3 changes: 2 additions & 1 deletion nodes/srl/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

log "github.com/sirupsen/logrus"
"github.com/srl-labs/containerlab/clab/exec"
"github.com/srl-labs/containerlab/utils"
"golang.org/x/mod/semver"
)

Expand Down Expand Up @@ -163,7 +164,7 @@ func (n *srl) setVersionSpecificParams(tplData *srlTemplateData) {
// in srlinux >= v23.10+ linuxadmin and admin user ssh keys can only be configured via the cli
// so we add the keys to the template data for rendering.
if len(n.sshPubKeys) > 0 && (semver.Compare(v, "v23.10") >= 0 || n.swVersion.Major == "0") {
tplData.SSHPubKeys = catenateKeys(n.sshPubKeys)
tplData.SSHPubKeys = utils.MarshalAndCatenateSSHPubKeys(n.sshPubKeys)
}

// in srlinux >= v24.3+ we add ACL rules to enable http and telnet access
Expand Down
34 changes: 34 additions & 0 deletions utils/ssh.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package utils

import (
"bytes"
"os/exec"
"regexp"
"strings"

log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
)

// GetSSHVersion returns the version of the ssh client
Expand Down Expand Up @@ -32,3 +35,34 @@ func parseSSHVersion(in string) string {

return match[1]
}

// MarshalSSHPubKeys marshales the ssh public keys
// and a string slice that contains string representations of the keys.
func MarshalSSHPubKeys(in []ssh.PublicKey) []string {
r := []string{}

for _, k := range in {
// extract the keys in AuthorizedKeys format (e.g. "ssh-rsa <KEY>")
ks := bytes.TrimSpace(ssh.MarshalAuthorizedKey(k))
r = append(r, string(ks))

}

return r
}

// MarshalAndCatenateSSHPubKeys catenates the ssh public keys
// and produces a string that can be used in the
// cli config command to set the ssh public keys
// for users.
// Each key value in the catenated string will be double quoted.
func MarshalAndCatenateSSHPubKeys(in []ssh.PublicKey) string {
keysSlice := MarshalSSHPubKeys(in)
quotedKeys := make([]string, len(keysSlice))

for i, k := range keysSlice {
quotedKeys[i] = "\"" + k + "\""
}

return strings.Join(quotedKeys, " ")
}
35 changes: 35 additions & 0 deletions utils/ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package utils

import (
"testing"

"github.com/google/go-cmp/cmp"
)

func TestParseSSHVersion(t *testing.T) {
Expand Down Expand Up @@ -36,3 +38,36 @@ func TestParseSSHVersion(t *testing.T) {
})
}
}

func Test_MarshalAndCatenateSSHPubKeys(t *testing.T) {
type fields struct {
keyFiles []string
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "test1",
fields: fields{
keyFiles: []string{"test_data/keys"},
},
want: "\"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs4Qv1yrBk6ygt+o7J4sUcYv+WfDjdAyABDoinOt3PgSmCcVqqAP2qS8UtTnMNuy93Orp6+/R/7/R3O5xdY6I4YViK3WVlKTAUVm7vdeTKp9uq1tNeWgo7+J3baSbQ3INp85ScTfFvRzRCFkr/W97Wh6pTa7ysgkcPvc2/tXG2z36Mx7/TFBk3Q1LY3ByKLtGrC5JnVpMTrqrsCwcLEVHHEZ4z5R4FZED/lpz+wTNFnR/l9HA6yDkKYensHynx+guqYpYD6y4yEGY/LcUnwBg0zIlUhmOsvdmxWBz12Lp7EBiNjSwhnPfe+o3efLGGnjWUAa4TgO8Sa8PQP0pK/ZNd\" \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILKdXYzPIq8kHRJtDrh21wMVI76AnuPk7HDLeDteKN74\"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
keys, err := LoadSSHPubKeysFromFiles(tt.fields.keyFiles)
if err != nil {
t.Errorf("failed to load keys: %v", err)
}

got := MarshalAndCatenateSSHPubKeys(keys)

if d := cmp.Diff(got, tt.want); d != "" {
t.Errorf("MarshalAndCatenateSSHPubKeys() = %s", d)
}
})
}
}
File renamed without changes.
Loading