diff --git a/cmd/deploy.go b/cmd/deploy.go index 1ff4b0bc7a..db24a7a6f0 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -147,11 +147,9 @@ func (dc *deployCmd) validate(cmd *cobra.Command, args []string) error { func autofillApimodel(dc *deployCmd) { var err error - if dc.containerService.Properties.LinuxProfile != nil { - if dc.containerService.Properties.LinuxProfile.AdminUsername == "" { - log.Warnf("apimodel: no linuxProfile.adminUsername was specified. Will use 'azureuser'.") - dc.containerService.Properties.LinuxProfile.AdminUsername = "azureuser" - } + if dc.containerService.Properties.LinuxProfile.AdminUsername == "" { + log.Warnf("apimodel: no linuxProfile.adminUsername was specified. Will use 'azureuser'.") + dc.containerService.Properties.LinuxProfile.AdminUsername = "azureuser" } if dc.dnsPrefix != "" && dc.containerService.Properties.MasterProfile.DNSPrefix != "" { @@ -183,13 +181,15 @@ func autofillApimodel(dc *deployCmd) { } } - if dc.containerService.Properties.LinuxProfile != nil && (dc.containerService.Properties.LinuxProfile.SSH.PublicKeys == nil || + if dc.containerService.Properties.LinuxProfile.SSH.PublicKeys == nil || len(dc.containerService.Properties.LinuxProfile.SSH.PublicKeys) == 0 || - dc.containerService.Properties.LinuxProfile.SSH.PublicKeys[0].KeyData == "") { - translator := &i18n.Translator{ - Locale: dc.locale, + dc.containerService.Properties.LinuxProfile.SSH.PublicKeys[0].KeyData == "" { + creator := &acsengine.SSHCreator{ + Translator: &i18n.Translator{ + Locale: dc.locale, + }, } - _, publicKey, err := acsengine.CreateSaveSSH(dc.containerService.Properties.LinuxProfile.AdminUsername, dc.outputDirectory, translator) + _, publicKey, err := creator.CreateSaveSSH(dc.containerService.Properties.LinuxProfile.AdminUsername, dc.outputDirectory) if err != nil { log.Fatal("Failed to generate SSH Key") } diff --git a/pkg/acsengine/ssh.go b/pkg/acsengine/ssh.go index bcee867057..2a955311d9 100644 --- a/pkg/acsengine/ssh.go +++ b/pkg/acsengine/ssh.go @@ -4,15 +4,26 @@ import ( "crypto/rand" "crypto/rsa" "fmt" + "io" - "github.com/Azure/acs-engine/pkg/helpers" "github.com/Azure/acs-engine/pkg/i18n" + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" ) -// CreateSaveSSH generates and stashes an SSH key pair. -func CreateSaveSSH(username, outputDirectory string, s *i18n.Translator) (privateKey *rsa.PrivateKey, publicKeyString string, err error) { +// SSHCreator represents the object that creates SSH key pair +type SSHCreator struct { + Translator *i18n.Translator +} + +const ( + // SSHKeySize is the size (in bytes) of SSH key to create + SSHKeySize = 4096 +) - privateKey, publicKeyString, err = helpers.CreateSSH(rand.Reader, s) +// CreateSaveSSH generates and stashes an SSH key pair. +func (s *SSHCreator) CreateSaveSSH(username, outputDirectory string) (privateKey *rsa.PrivateKey, publicKeyString string, err error) { + privateKey, publicKeyString, err = s.CreateSSH(rand.Reader) if err != nil { return nil, "", err } @@ -20,7 +31,7 @@ func CreateSaveSSH(username, outputDirectory string, s *i18n.Translator) (privat privateKeyPem := privateKeyToPem(privateKey) f := &FileSaver{ - Translator: s, + Translator: s.Translator, } err = f.SaveFile(outputDirectory, fmt.Sprintf("%s_rsa", username), privateKeyPem) @@ -30,3 +41,22 @@ func CreateSaveSSH(username, outputDirectory string, s *i18n.Translator) (privat return privateKey, publicKeyString, nil } + +// CreateSSH creates an SSH key pair. +func (s *SSHCreator) CreateSSH(rg io.Reader) (privateKey *rsa.PrivateKey, publicKeyString string, err error) { + log.Debugf("ssh: generating %dbit rsa key", SSHKeySize) + privateKey, err = rsa.GenerateKey(rg, SSHKeySize) + if err != nil { + return nil, "", s.Translator.Errorf("failed to generate private key for ssh: %q", err) + } + + publicKey := privateKey.PublicKey + sshPublicKey, err := ssh.NewPublicKey(&publicKey) + if err != nil { + return nil, "", s.Translator.Errorf("failed to create openssh public key string: %q", err) + } + authorizedKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey) + authorizedKey := string(authorizedKeyBytes) + + return privateKey, authorizedKey, nil +} diff --git a/pkg/acsengine/ssh_test.go b/pkg/acsengine/ssh_test.go index fe4f82f96a..4c6a1c740a 100644 --- a/pkg/acsengine/ssh_test.go +++ b/pkg/acsengine/ssh_test.go @@ -3,10 +3,6 @@ package acsengine import ( "math/rand" "testing" - - "github.com/Azure/acs-engine/pkg/helpers" - - "github.com/Azure/acs-engine/pkg/i18n" ) func TestCreateSSH(t *testing.T) { @@ -67,11 +63,11 @@ EPDesL0rH+3s1CKpgkhYdbJ675GFoGoq+X21QaqsdvoXmmuJF9qq9Tq+JaWloUNq -----END RSA PRIVATE KEY----- ` - translator := &i18n.Translator{ - Locale: nil, + creator := &SSHCreator{ + Translator: nil, } - privateKey, publicKey, err := helpers.CreateSSH(rg, translator) + privateKey, publicKey, err := creator.CreateSSH(rg) if err != nil { t.Fatalf("failed to generate SSH: %s", err) } diff --git a/pkg/acsengine/testdata/agentPoolOnly/v20180331/agents.json b/pkg/acsengine/testdata/agentPoolOnly/v20180331/agents.json deleted file mode 100644 index bb4edfb639..0000000000 --- a/pkg/acsengine/testdata/agentPoolOnly/v20180331/agents.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "apiVersion": "2018-03-31", - "properties": { - "dnsPrefix": "agents006", - "fqdn": "agents006.azmk8s.io", - "kubernetesVersion": "1.8.7", - "agentPoolProfiles": [ - { - "name": "agentpool1", - "count": 1, - "vmSize": "Standard_D2_v2" - } - ], - "servicePrincipalProfile": { - "clientID": "ServicePrincipalClientID", - "secret": "myServicePrincipalClientSecret" - } - } -} diff --git a/pkg/api/agentPoolOnlyApi/v20180331/validate.go b/pkg/api/agentPoolOnlyApi/v20180331/validate.go index 9176143639..61d2ec171b 100644 --- a/pkg/api/agentPoolOnlyApi/v20180331/validate.go +++ b/pkg/api/agentPoolOnlyApi/v20180331/validate.go @@ -84,12 +84,9 @@ func (a *Properties) Validate() error { } } - if a.LinuxProfile != nil { - if e := a.LinuxProfile.Validate(); e != nil { - return e - } + if e := a.LinuxProfile.Validate(); e != nil { + return e } - if e := validateVNET(a); e != nil { return e } diff --git a/pkg/api/apiloader.go b/pkg/api/apiloader.go index 02bade36e1..f381b0c9ae 100644 --- a/pkg/api/apiloader.go +++ b/pkg/api/apiloader.go @@ -1,7 +1,6 @@ package api import ( - "crypto/rand" "encoding/json" "io/ioutil" "reflect" @@ -46,7 +45,7 @@ func (a *Apiloader) DeserializeContainerService(contents []byte, validate, isUpd if service == nil || err != nil { if isAgentPoolOnlyClusterJSON(contents) { log.Info("No masterProfile: interpreting API model as agent pool only") - service, _, err := a.LoadContainerServiceForAgentPoolOnlyCluster(contents, version, validate, isUpdate, "") + service, err := a.LoadContainerServiceForAgentPoolOnlyCluster(contents, version, validate, isUpdate, "") if service == nil || err != nil { log.Infof("Error returned by LoadContainerServiceForAgentPoolOnlyCluster: %+v", err) } @@ -186,72 +185,59 @@ func (a *Apiloader) LoadContainerService( } // LoadContainerServiceForAgentPoolOnlyCluster loads an ACS Cluster API Model, validates it, and returns the unversioned representation -func (a *Apiloader) LoadContainerServiceForAgentPoolOnlyCluster(contents []byte, version string, validate, isUpdate bool, defaultKubernetesVersion string) (*ContainerService, bool, error) { - IsSSHAutoGenerated := false +func (a *Apiloader) LoadContainerServiceForAgentPoolOnlyCluster(contents []byte, version string, validate, isUpdate bool, defaultKubernetesVersion string) (*ContainerService, error) { switch version { case v20170831.APIVersion: managedCluster := &v20170831.ManagedCluster{} if e := json.Unmarshal(contents, &managedCluster); e != nil { - return nil, IsSSHAutoGenerated, e + return nil, e } // verify orchestrator version if len(managedCluster.Properties.KubernetesVersion) > 0 && !common.AllKubernetesSupportedVersions[managedCluster.Properties.KubernetesVersion] { - return nil, IsSSHAutoGenerated, a.Translator.Errorf("The selected orchestrator version '%s' is not supported", managedCluster.Properties.KubernetesVersion) + return nil, a.Translator.Errorf("The selected orchestrator version '%s' is not supported", managedCluster.Properties.KubernetesVersion) } // use defaultKubernetesVersion arg if no version was supplied in the request contents if managedCluster.Properties.KubernetesVersion == "" && defaultKubernetesVersion != "" { if !common.AllKubernetesSupportedVersions[defaultKubernetesVersion] { - return nil, IsSSHAutoGenerated, a.Translator.Errorf("The selected orchestrator version '%s' is not supported", defaultKubernetesVersion) + return nil, a.Translator.Errorf("The selected orchestrator version '%s' is not supported", defaultKubernetesVersion) } managedCluster.Properties.KubernetesVersion = defaultKubernetesVersion } if e := managedCluster.Properties.Validate(); validate && e != nil { - return nil, IsSSHAutoGenerated, e + return nil, e } - return ConvertV20170831AgentPoolOnly(managedCluster), false, nil + return ConvertV20170831AgentPoolOnly(managedCluster), nil case v20180331.APIVersion: managedCluster := &v20180331.ManagedCluster{} if e := json.Unmarshal(contents, &managedCluster); e != nil { - return nil, IsSSHAutoGenerated, e + return nil, e } // verify orchestrator version if len(managedCluster.Properties.KubernetesVersion) > 0 && !common.AllKubernetesSupportedVersions[managedCluster.Properties.KubernetesVersion] { - return nil, IsSSHAutoGenerated, a.Translator.Errorf("The selected orchestrator version '%s' is not supported", managedCluster.Properties.KubernetesVersion) + return nil, a.Translator.Errorf("The selected orchestrator version '%s' is not supported", managedCluster.Properties.KubernetesVersion) } // use defaultKubernetesVersion arg if no version was supplied in the request contents if managedCluster.Properties.KubernetesVersion == "" && defaultKubernetesVersion != "" { if !common.AllKubernetesSupportedVersions[defaultKubernetesVersion] { - return nil, IsSSHAutoGenerated, a.Translator.Errorf("The selected orchestrator version '%s' is not supported", defaultKubernetesVersion) + return nil, a.Translator.Errorf("The selected orchestrator version '%s' is not supported", defaultKubernetesVersion) } managedCluster.Properties.KubernetesVersion = defaultKubernetesVersion } if e := managedCluster.Properties.Validate(); validate && e != nil { - return nil, IsSSHAutoGenerated, e - } - - if managedCluster.Properties.LinuxProfile == nil { - linuxProfile := &v20180331.LinuxProfile{} - linuxProfile.AdminUsername = "azureuser" - _, publicKey, err := helpers.CreateSSH(rand.Reader, a.Translator) - if err != nil { - return nil, IsSSHAutoGenerated, err - } - linuxProfile.SSH.PublicKeys = []v20180331.PublicKey{{KeyData: publicKey}} - managedCluster.Properties.LinuxProfile = linuxProfile - IsSSHAutoGenerated = true + return nil, e } - return ConvertV20180331AgentPoolOnly(managedCluster), IsSSHAutoGenerated, nil + return ConvertV20180331AgentPoolOnly(managedCluster), nil case apvlabs.APIVersion: managedCluster := &apvlabs.ManagedCluster{} if e := json.Unmarshal(contents, &managedCluster); e != nil { - return nil, IsSSHAutoGenerated, e + return nil, e } if e := managedCluster.Properties.Validate(); validate && e != nil { - return nil, IsSSHAutoGenerated, e + return nil, e } - return ConvertVLabsAgentPoolOnly(managedCluster), IsSSHAutoGenerated, nil + return ConvertVLabsAgentPoolOnly(managedCluster), nil default: - return nil, IsSSHAutoGenerated, a.Translator.Errorf("unrecognized APIVersion in LoadContainerServiceForAgentPoolOnlyCluster '%s'", version) + return nil, a.Translator.Errorf("unrecognized APIVersion in LoadContainerServiceForAgentPoolOnlyCluster '%s'", version) } } diff --git a/pkg/api/convertertoagentpoolonlyapi.go b/pkg/api/convertertoagentpoolonlyapi.go index 65935f1b74..ff0bd60670 100644 --- a/pkg/api/convertertoagentpoolonlyapi.go +++ b/pkg/api/convertertoagentpoolonlyapi.go @@ -317,7 +317,6 @@ func convertV20180331AgentPoolOnlyProperties(obj *v20180331.Properties) *Propert if obj.LinuxProfile != nil { properties.LinuxProfile = convertV20180331AgentPoolOnlyLinuxProfile(obj.LinuxProfile) } - if obj.WindowsProfile != nil { properties.WindowsProfile = convertV20180331AgentPoolOnlyWindowsProfile(obj.WindowsProfile) } diff --git a/pkg/helpers/helpers.go b/pkg/helpers/helpers.go index 2cade4d925..9affc4fac2 100644 --- a/pkg/helpers/helpers.go +++ b/pkg/helpers/helpers.go @@ -3,17 +3,7 @@ package helpers import ( // "fmt" "bytes" - "crypto/rsa" "encoding/json" - "io" - - "github.com/Azure/acs-engine/pkg/i18n" - "golang.org/x/crypto/ssh" -) - -const ( - // SSHKeySize is the size (in bytes) of SSH key to create - SSHKeySize = 4096 ) // JSONMarshalIndent marshals formatted JSON w/ optional SetEscapeHTML @@ -56,21 +46,3 @@ func PointerToBool(b bool) *bool { p := b return &p } - -// CreateSSH creates an SSH key pair. -func CreateSSH(rg io.Reader, s *i18n.Translator) (privateKey *rsa.PrivateKey, publicKeyString string, err error) { - privateKey, err = rsa.GenerateKey(rg, SSHKeySize) - if err != nil { - return nil, "", s.Errorf("failed to generate private key for ssh: %q", err) - } - - publicKey := privateKey.PublicKey - sshPublicKey, err := ssh.NewPublicKey(&publicKey) - if err != nil { - return nil, "", s.Errorf("failed to create openssh public key string: %q", err) - } - authorizedKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey) - authorizedKey := string(authorizedKeyBytes) - - return privateKey, authorizedKey, nil -}