From ba362c0eeba3e3f8f96d2ee5cc4c206356c00262 Mon Sep 17 00:00:00 2001 From: Vladimir Ermakov Date: Wed, 10 Jul 2024 17:31:09 +0200 Subject: [PATCH] fix dynamic ssh key not saved --- connection_ssh.go | 6 +++--- go.mod | 7 ++++++ go.sum | 15 +++++++++++++ provider.go | 49 +++++++++++++++++++++++++++++++++++------- utils.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++ utils_test.go | 26 +++++++++++++++++++++++ 6 files changed, 146 insertions(+), 11 deletions(-) diff --git a/connection_ssh.go b/connection_ssh.go index c88e3df..5df3504 100644 --- a/connection_ssh.go +++ b/connection_ssh.go @@ -21,7 +21,7 @@ type PrivPub interface { } // initSSHKey prepare dynamic ssh key for flatcar instances -func (g *InstanceGroup) initSSHKey(_ context.Context, log hclog.Logger, settings provider.Settings) error { +func (g *InstanceGroup) initSSHKey(_ context.Context, log hclog.Logger, settings *provider.Settings) error { var key PrivPub var err error @@ -64,8 +64,8 @@ func (g *InstanceGroup) initSSHKey(_ context.Context, log hclog.Logger, settings log.With("public_key", g.sshPubKey).Debug("Extracted public key") if g.imgProps != nil { - if g.imgProps.OSAdminUser != "" && settings.ConnectorConfig.Username == "" { - settings.ConnectorConfig.Username = g.imgProps.OSAdminUser + if g.imgProps.OSAdminUser != "" && settings.Username == "" { + settings.Username = g.imgProps.OSAdminUser } } diff --git a/go.mod b/go.mod index 820e585..be16ad0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/sardinasystems/fleeting-plugin-openstack go 1.22 require ( + github.com/coreos/ignition/v2 v2.19.0 + github.com/coreos/vcontext v0.0.0-20231102161604-685dc7299dc5 github.com/gophercloud/gophercloud/v2 v2.0.0 github.com/hashicorp/go-hclog v1.6.3 github.com/jinzhu/copier v0.4.0 @@ -15,8 +17,12 @@ require ( require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 // indirect + github.com/aws/aws-sdk-go v1.54.17 // indirect github.com/bodgit/ntlmssp v0.0.0-20240506230425-31973bb52d9b // indirect github.com/bodgit/windows v1.0.1 // indirect + github.com/coreos/go-json v0.0.0-20231102161613-e49c8866685a // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.17.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -40,6 +46,7 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde // indirect + github.com/vincent-petithory/dataurl v1.0.0 // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sys v0.22.0 // indirect diff --git a/go.sum b/go.sum index 91cfa4f..8420679 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,24 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 h1:w0E0fgc1YafGEh5cROhlROMWXiNoZqApk2PDN0M1+Ns= github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= +github.com/aws/aws-sdk-go v1.54.17 h1:ZV/qwcCIhMHgsJ6iXXPVYI0s1MdLT+5LW28ClzCUPeI= +github.com/aws/aws-sdk-go v1.54.17/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bodgit/ntlmssp v0.0.0-20240506230425-31973bb52d9b h1:baFN6AnR0SeC194X2D292IUZcHDs4JjStpqtE70fjXE= github.com/bodgit/ntlmssp v0.0.0-20240506230425-31973bb52d9b/go.mod h1:Ram6ngyPDmP+0t6+4T2rymv0w0BS9N8Ch5vvUJccw5o= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/coreos/go-json v0.0.0-20231102161613-e49c8866685a h1:QimUZQ6Au5wFKKkPMmdoXen+CNR66lXt/76AQLBltS0= +github.com/coreos/go-json v0.0.0-20231102161613-e49c8866685a/go.mod h1:rcFZM3uxVvdyNmsAV2jopgPD1cs5SPWJWU5dOz2LUnw= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/ignition/v2 v2.19.0 h1:ek200E31M1NCVyvL22Bd40kOJp7yt1gdHAb3xwqTi8Y= +github.com/coreos/ignition/v2 v2.19.0/go.mod h1:ydb815SaH9A4304wIUoCS5IHyKRHWEp7dfJH8cQW2gA= +github.com/coreos/vcontext v0.0.0-20231102161604-685dc7299dc5 h1:sMZSC2BW5LKCdvNbfN12SbKrNvtLBUNjfHZmMvI2ItY= +github.com/coreos/vcontext v0.0.0-20231102161604-685dc7299dc5/go.mod h1:Salmysdw7DAVuobBW/LwsKKgpyCPHUhjyJoMJD+ZJiI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -16,6 +28,7 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -92,6 +105,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8= +github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= +github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/gitlab-org/fleeting/fleeting v0.0.0-20240708113315-42aa0a457699 h1:5AY/RECILd75l93UcyRj4jCKSakfREsQGocrLhnt62E= gitlab.com/gitlab-org/fleeting/fleeting v0.0.0-20240708113315-42aa0a457699/go.mod h1:jxlU7KMun1XSMt5QvlraEffPHOnU4sxZQxJ1aBhfoOg= diff --git a/provider.go b/provider.go index 50e0937..8cb5c19 100644 --- a/provider.go +++ b/provider.go @@ -88,17 +88,19 @@ func (g *InstanceGroup) Init(ctx context.Context, log hclog.Logger, settings pro g.imgProps = imgProps } - log.With("creds", settings).Info("settings") + // log.With("creds", settings, "image", g.imgProps).Info("settings 1") if !g.UseIgnition && !settings.ConnectorConfig.UseStaticCredentials { return provider.ProviderInfo{}, fmt.Errorf("Only static credentials supported in Cloud-Init mode.") } - err = g.initSSHKey(ctx, log, settings) + err = g.initSSHKey(ctx, log, &settings) if err != nil { return provider.ProviderInfo{}, err } + // log.With("creds", settings, "image", g.imgProps).Info("settings2") + if g.BootTimeS != "" { g.BootTime, err = time.ParseDuration(g.BootTimeS) if err != nil { @@ -260,7 +262,10 @@ func (g *InstanceGroup) createInstance(ctx context.Context) (string, error) { } if g.UseIgnition { - // injectSSHKeyIgn(spec) + err := InsertSSHKeyIgn(spec, g.settings.Username, g.sshPubKey) + if err != nil { + return "", err + } } srv, err := servers.Create(ctx, g.computeClient, spec, hintOpts).Extract() @@ -312,13 +317,41 @@ func (g *InstanceGroup) ConnectInfo(ctx context.Context, instanceID string) (pro InternalAddr: ipAddr, ExternalAddr: ipAddr, } - - // TODO: get image metadata and get os_admin_user - // TODO: get from image meta - info.OS = "linux" - info.Arch = "amd64" info.Protocol = provider.ProtocolSSH + if g.imgProps != nil { + switch g.imgProps.OSType { + case "", "linux": + info.Protocol = provider.ProtocolSSH + info.OS = "linux" + + case "windows": + g.log.Warn("Windows not really supported by the plugin.") + info.Protocol = provider.ProtocolWinRM + info.OS = g.imgProps.OSType + + default: + g.log.Warn("Unknown image os_type", "os_type", g.imgProps.OSType) + info.OS = g.imgProps.OSType + } + + switch g.imgProps.Architecture { + case "", "x86_64": + info.Arch = "amd64" + + case "aarch64": + info.Arch = "arm64" + + default: + g.log.Warn("Unknown image arch", "arch", g.imgProps.Architecture) + } + + } else { + // default to linux on amd64 + info.OS = "linux" + info.Arch = "amd64" + } + // g.log.Debug("Info", "info", info) inp := bytes.NewBuffer(nil) diff --git a/utils.go b/utils.go index 0f26b3f..7f48d19 100644 --- a/utils.go +++ b/utils.go @@ -2,11 +2,15 @@ package fpoc import ( "context" + "encoding/json" "fmt" "maps" "regexp" "strings" + igncfg "github.com/coreos/ignition/v2/config/v3_4" + igntyp "github.com/coreos/ignition/v2/config/v3_4/types" + "github.com/coreos/vcontext/report" "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/v2/openstack/image/v2/images" @@ -166,3 +170,53 @@ func GetImageProperties(ctx context.Context, cli *gophercloud.ServiceClient, ima return out, nil } + +func InsertSSHKeyIgn(spec *ExtCreateOpts, username, pubKey string) error { + var cfg igntyp.Config + var err error + + if spec.UserData != "" { + var rpt report.Report + + cfg, rpt, err = igncfg.ParseCompatibleVersion([]byte(spec.UserData)) + if err != nil { + return fmt.Errorf("failed to parse ignition: %w", err) + } + + _ = rpt + } + + if cfg.Ignition.Version == "" { + cfg.Ignition.Version = igntyp.MaxVersion.String() + } + + var user *igntyp.PasswdUser + if cfg.Passwd.Users == nil { + cfg.Passwd.Users = make([]igntyp.PasswdUser, 0) + } + + for idx, lu := range cfg.Passwd.Users { + if lu.Name == username { + user = &cfg.Passwd.Users[idx] + break + } + } + if user == nil { + cfg.Passwd.Users = append(cfg.Passwd.Users, igntyp.PasswdUser{Name: username}) + user = &cfg.Passwd.Users[len(cfg.Passwd.Users)-1] + } + + if user.SSHAuthorizedKeys == nil { + user.SSHAuthorizedKeys = make([]igntyp.SSHAuthorizedKey, 0) + } + + user.SSHAuthorizedKeys = append(user.SSHAuthorizedKeys, igntyp.SSHAuthorizedKey(pubKey)) + + buf, err := json.Marshal(cfg) + if err != nil { + return fmt.Errorf("failed to marshal ignition: %w", err) + } + + spec.UserData = string(buf) + return nil +} diff --git a/utils_test.go b/utils_test.go index 1ea0652..72ffe6d 100644 --- a/utils_test.go +++ b/utils_test.go @@ -133,3 +133,29 @@ func TestGetImageProperties(t *testing.T) { t.Log(props) } + +func TestInsertSSHKeyIgn(t *testing.T) { + testCases := []struct { + name string + userData string + expected string + }{ + {"empty", "", `{"ignition":{"config":{"replace":{"verification":{}}},"proxy":{},"security":{"tls":{}},"timeouts":{},"version":"3.4.0"},"kernelArguments":{},"passwd":{"users":[{"name":"test","sshAuthorizedKeys":["testkey"]}]},"storage":{},"systemd":{}}`}, + {"diff-user", `{"ignition":{"version":"3.3.0"},"passwd":{"users":[{"name":"test2"}]}}`, `{"ignition":{"config":{"replace":{"verification":{}}},"proxy":{},"security":{"tls":{}},"timeouts":{},"version":"3.4.0"},"kernelArguments":{},"passwd":{"users":[{"name":"test2"},{"name":"test","sshAuthorizedKeys":["testkey"]}]},"storage":{},"systemd":{}}`}, + {"same-user", `{"ignition":{"version":"3.2.0"},"passwd":{"users":[{"name":"test","sshAuthorizedKeys":["testkey1"]}]}}`, `{"ignition":{"config":{"replace":{"verification":{}}},"proxy":{},"security":{"tls":{}},"timeouts":{},"version":"3.4.0"},"kernelArguments":{},"passwd":{"users":[{"name":"test","sshAuthorizedKeys":["testkey1","testkey"]}]},"storage":{},"systemd":{}}`}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + + spec := &ExtCreateOpts{ + UserData: tc.userData, + } + + err := InsertSSHKeyIgn(spec, "test", "testkey") + assert.NoError(err) + assert.Equal(tc.expected, spec.UserData) + }) + } +}