From fc30368d384bbe482af9a2d7c6dcdb36281b3757 Mon Sep 17 00:00:00 2001 From: dmitsh Date: Wed, 31 Oct 2018 21:53:33 -0700 Subject: [PATCH] allow passing public SSH keys from the command line (#13) * allow passing public SSH keys from the command line * update docs --- cmd/generate.go | 25 +++++++++++---- docs/deployment.md | 10 ++---- docs/properties.md | 72 ++++++++++++++++++++++++++++++++++++++++++++ pkg/api/apiloader.go | 16 +++++++--- 4 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 docs/properties.md diff --git a/cmd/generate.go b/cmd/generate.go index 5a2e075..f354d2c 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -3,7 +3,9 @@ package cmd import ( "errors" "fmt" + "io/ioutil" "os" + "strings" "github.com/Microsoft/oe-engine/pkg/api" "github.com/Microsoft/oe-engine/pkg/engine" @@ -15,13 +17,13 @@ import ( const ( generateName = "generate" generateShortDescription = "Generate an Azure Resource Manager template" - generateLongDescription = "Generates an Azure Resource Manager template, parameters file and other assets for a cluster" + generateLongDescription = "Generates an Azure Resource Manager template and parameters file" ) type generateCmd struct { apimodelPath string - outputDirectory string // can be auto-determined from clusterDefinition - classicMode bool + outputDirectory string + sshPubKeys []string noPrettyPrint bool parametersOnly bool @@ -51,8 +53,8 @@ func newGenerateCmd() *cobra.Command { f := generateCmd.Flags() f.StringVar(&gc.apimodelPath, "api-model", "", "") - f.StringVar(&gc.outputDirectory, "output-directory", "", "output directory (derived from FQDN if absent)") - f.BoolVar(&gc.classicMode, "classic-mode", false, "enable classic parameters and outputs") + f.StringVar(&gc.outputDirectory, "output-directory", "", "output directory (_output if absent)") + f.StringArrayVar(&gc.sshPubKeys, "ssh-public-key", nil, "SSH public key file path") f.BoolVar(&gc.noPrettyPrint, "no-pretty-print", false, "skip pretty printing the output") f.BoolVar(&gc.parametersOnly, "parameters-only", false, "only output parameters files") @@ -77,6 +79,17 @@ func (gc *generateCmd) validate(cmd *cobra.Command, args []string) error { return fmt.Errorf(fmt.Sprintf("specified api model does not exist (%s)", gc.apimodelPath)) } + for i, keyPath := range gc.sshPubKeys { + if _, err := os.Stat(keyPath); os.IsNotExist(err) { + return fmt.Errorf(fmt.Sprintf("ssh public key file %s does not exist", keyPath)) + } + b, err := ioutil.ReadFile(keyPath) + if err != nil { + return err + } + gc.sshPubKeys[i] = strings.TrimSpace(string(b)) + } + return nil } @@ -84,7 +97,7 @@ func (gc *generateCmd) loadAPIModel(cmd *cobra.Command, args []string) error { var err error apiloader := &api.Apiloader{} - gc.oe, err = apiloader.LoadOpenEnclaveFromFile(gc.apimodelPath, true, false) + gc.oe, err = apiloader.LoadOpenEnclaveFromFile(gc.apimodelPath, true, false, gc.sshPubKeys) if err != nil { return fmt.Errorf(fmt.Sprintf("error parsing the api model: %s", err.Error())) } diff --git a/docs/deployment.md b/docs/deployment.md index 46b15bd..a69464c 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -12,6 +12,9 @@ Alternatively, you can download latest release from [here](https://github.com/Mi ### Create VM definition file The VM definition file is a JSON-formatted description of the properties of the VMs, such as: compute power, OS image, credentials, etc. + +For details, refer to the [Setting properties in the VM definition file](properties.md) + The examples below illustrate how to set the various properties. * [Multi-VM deployment](examples/oe-multi-vm.json) - Deploying multiple VMs @@ -21,13 +24,6 @@ The examples below illustrate how to set the various properties. * [Linux user password](examples/oe-lnx-passwd.json) - Using password authentication instead of SSH on Linux * [Windows OpenSSH](examples/oe-win-ssh.json) - Installing and configuring OpenSSH on Windows -The table below summarizes enumerated properties: - -| Property | Key | Values | -| ------ | ------ |------ | -| OS | `osImageName` | `UbuntuServer_16.04` `WindowsServer_2016` | -| Compute| `vmSize` | `Standard_DC2s` `Standard_DC4s` | - ## Generate deployment template `oe-engine` generates 3 files: diff --git a/docs/properties.md b/docs/properties.md new file mode 100644 index 0000000..60db5a8 --- /dev/null +++ b/docs/properties.md @@ -0,0 +1,72 @@ +# Setting properties in the VM definition file + +This document describes VM properties, configurable in the VM definition file + +### VM name +Specifies VM name. + +* Path: `properties/vmProfiles[]/name` +* Value: string (follows the rules of underlying OS) + +### OS image +Specifies the type and the version of the operating system. + +* Path: `properties/vmProfiles[]/osImageName` +* Values: + * `UbuntuServer_16.04` + * `WindowsServer_2016` + +### OS disk type +Specifies OS disk characteristics. + +* Path: `properties/vmProfiles[]/osImageName` +* Values: + * `Premium_LRS` - Premium SSD + * `StandardSSD_LRS` - Standard SSD + * `Standard_LRS` - Standard HDD + +### VM compute power +Specifies VM compute characteristics + +* Path: `properties/vmProfiles[]/vmSize` +* Values: + * `Standard_DC2s` + * `Standard_DC4s` + +Refer to the [VM sizes in Azure](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes) for more details. + +### VM Software +Indicates whether Open Enclave SDK and its dependencies should be installed or not. + +* Path: `properties/vmProfiles[]/isVanilla` +* Values: boolean + * `true` - a vanilla VM. Open Enclave SDK will not be installed + * `false` - not a vanilla VM. Open Enclave SDK will be installed and verified + +### Linux credentials +If at least one of the VMs runs Linux, `linuxProfile` must be present and contain admin user name and password or public SSH key. +Multiple public keys are **supported**. +Setting both the password and the public key(s) is **not allowed**. + +* Path: `properties/linuxProfile/adminUsername` +* Value: string + +* Path: `properties/linuxProfile/adminPassword` +* Value: string + +* Path: `properties/linuxProfile/sshPublicKeys[]/keyData` +* Value: public SSH key + +The public SSH key(s) could also be set from the command line using `--ssh-public-key` argument. +```sh +oe-engine generate oe-vm.json --ssh-public-key .ssh/id_rsa1.pub --ssh-public-key .ssh/id_rsa2.pub +``` + +### Windows credentials +If at least one of the VMs runs Windows, `windowsProfile` must be present and contain admin user name and password. + +* Path: `properties/windowsProfile/adminUsername` +* Value: string + +* Path: `properties/windowsProfile/adminPassword` +* Value: string diff --git a/pkg/api/apiloader.go b/pkg/api/apiloader.go index 18a9fdf..312c557 100644 --- a/pkg/api/apiloader.go +++ b/pkg/api/apiloader.go @@ -15,17 +15,17 @@ type Apiloader struct { } // LoadOpenEnclaveFromFile loads an OE API Model from a JSON file -func (a *Apiloader) LoadOpenEnclaveFromFile(jsonFile string, validate, isUpdate bool) (*OpenEnclave, error) { +func (a *Apiloader) LoadOpenEnclaveFromFile(jsonFile string, validate, isUpdate bool, sshPubKeys []string) (*OpenEnclave, error) { contents, e := ioutil.ReadFile(jsonFile) if e != nil { return nil, fmt.Errorf("error reading file %s: %s", jsonFile, e.Error()) } - return a.DeserializeOpenEnclave(contents, validate, isUpdate) + return a.DeserializeOpenEnclave(contents, validate, isUpdate, sshPubKeys) } // DeserializeOpenEnclave loads an ACS Cluster API Model, validates it, and returns the unversioned representation -func (a *Apiloader) DeserializeOpenEnclave(contents []byte, validate, isUpdate bool) (*OpenEnclave, error) { - oe, err := a.LoadOpenEnclave(contents, validate, isUpdate) +func (a *Apiloader) DeserializeOpenEnclave(contents []byte, validate, isUpdate bool, sshPubKeys []string) (*OpenEnclave, error) { + oe, err := a.LoadOpenEnclave(contents, validate, isUpdate, sshPubKeys) if oe == nil || err != nil { log.Infof("Error returned by LoadOpenEnclave: %+v", err) } @@ -33,7 +33,7 @@ func (a *Apiloader) DeserializeOpenEnclave(contents []byte, validate, isUpdate b } // LoadOpenEnclave loads and validates an OE API Model -func (a *Apiloader) LoadOpenEnclave(contents []byte, validate, isUpdate bool) (*OpenEnclave, error) { +func (a *Apiloader) LoadOpenEnclave(contents []byte, validate, isUpdate bool, sshPubKeys []string) (*OpenEnclave, error) { oe := &OpenEnclave{} if e := json.Unmarshal(contents, oe); e != nil { return nil, e @@ -41,6 +41,12 @@ func (a *Apiloader) LoadOpenEnclave(contents []byte, validate, isUpdate bool) (* if e := checkJSONKeys(contents, reflect.TypeOf(*oe)); e != nil { return nil, e } + // add SSH public keys from command line arguments + if oe.Properties.LinuxProfile != nil { + for _, key := range sshPubKeys { + oe.Properties.LinuxProfile.SSHPubKeys = append(oe.Properties.LinuxProfile.SSHPubKeys, &PublicKey{KeyData: key}) + } + } if e := oe.Properties.Validate(isUpdate); validate && e != nil { return nil, e }