github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cloudconfig/userdatacfg_win.go (about)

     1  // Copyright 2012, 2013, 2014, 2015 Canonical Ltd.
     2  // Copyright 2014, 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package cloudconfig
     6  
     7  import (
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"fmt"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/names"
    16  	"github.com/juju/utils/featureflag"
    17  	"github.com/juju/utils/series"
    18  
    19  	"github.com/juju/juju/cert"
    20  	"github.com/juju/juju/juju/osenv"
    21  	"github.com/juju/juju/juju/paths"
    22  	"github.com/juju/juju/tools"
    23  )
    24  
    25  type aclType string
    26  
    27  const (
    28  	fileSystem    aclType = "FileSystem"
    29  	registryEntry aclType = "Registry"
    30  )
    31  
    32  type windowsConfigure struct {
    33  	baseConfigure
    34  }
    35  
    36  // Configure updates the provided cloudinit.Config with
    37  // configuration to initialize a Juju machine agent.
    38  func (w *windowsConfigure) Configure() error {
    39  	if err := w.ConfigureBasic(); err != nil {
    40  		return err
    41  	}
    42  	return w.ConfigureJuju()
    43  }
    44  
    45  func (w *windowsConfigure) ConfigureBasic() error {
    46  
    47  	tmpDir, err := paths.TempDir(w.icfg.Series)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	renderer := w.conf.ShellRenderer()
    52  	dataDir := renderer.FromSlash(w.icfg.DataDir)
    53  	baseDir := renderer.FromSlash(filepath.Dir(tmpDir))
    54  	binDir := renderer.Join(baseDir, "bin")
    55  
    56  	w.conf.AddScripts(fmt.Sprintf(`%s`, winPowershellHelperFunctions))
    57  
    58  	// The jujud user only gets created on non-nano versions for now.
    59  	if !series.IsWindowsNano(w.icfg.Series) {
    60  		w.conf.AddScripts(fmt.Sprintf(`%s`, addJujudUser))
    61  	}
    62  
    63  	w.conf.AddScripts(
    64  		// Some providers create a baseDir before this step, but we need to
    65  		// make sure it exists before applying icacls
    66  		fmt.Sprintf(`mkdir -Force "%s"`, renderer.FromSlash(baseDir)),
    67  		fmt.Sprintf(`mkdir %s`, renderer.FromSlash(tmpDir)),
    68  		fmt.Sprintf(`mkdir "%s"`, binDir),
    69  		fmt.Sprintf(`mkdir "%s\locks"`, renderer.FromSlash(dataDir)),
    70  		`setx /m PATH "$env:PATH;C:\Juju\bin\"`,
    71  		// This is necessary for setACLs to work
    72  		`$adminsGroup = (New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount])`,
    73  		fmt.Sprintf(`icacls "%s" /inheritance:r /grant "${adminsGroup}:(OI)(CI)(F)" /t`, renderer.FromSlash(baseDir)),
    74  	)
    75  
    76  	// TODO(bogdanteleaga): This, together with the call above, should be using setACLs, once it starts working across all windows versions properly.
    77  	// Until then, if we change permissions, both this and setACLs should be changed to do the same thing.
    78  	if !series.IsWindowsNano(w.icfg.Series) {
    79  		w.conf.AddScripts(fmt.Sprintf(`icacls "%s" /inheritance:r /grant "jujud:(OI)(CI)(F)" /t`, renderer.FromSlash(baseDir)))
    80  	}
    81  
    82  	noncefile := renderer.Join(dataDir, NonceFile)
    83  	w.conf.AddScripts(
    84  		fmt.Sprintf(`Set-Content "%s" "%s"`, noncefile, shquote(w.icfg.MachineNonce)),
    85  	)
    86  	return nil
    87  }
    88  
    89  func (w *windowsConfigure) ConfigureJuju() error {
    90  	if err := w.icfg.VerifyConfig(); err != nil {
    91  		return errors.Trace(err)
    92  	}
    93  	if w.icfg.Bootstrap == true {
    94  		// Bootstrap machine not supported on windows
    95  		return errors.Errorf("bootstrapping is not supported on windows")
    96  	}
    97  
    98  	tools := w.icfg.ToolsList()[0]
    99  	toolsJson, err := json.Marshal(tools)
   100  	if err != nil {
   101  		return errors.Annotate(err, "while serializing the tools")
   102  	}
   103  
   104  	renderer := w.conf.ShellRenderer()
   105  	w.conf.AddScripts(
   106  		fmt.Sprintf(`$binDir="%s"`, renderer.FromSlash(w.icfg.JujuTools())),
   107  		fmt.Sprintf(`mkdir '%s'`, renderer.FromSlash(w.icfg.LogDir)),
   108  		`mkdir $binDir`,
   109  	)
   110  
   111  	toolsDownloadCmds, err := addDownloadToolsCmds(
   112  		w.icfg.Series, w.icfg.MongoInfo.CACert, w.icfg.ToolsList(),
   113  	)
   114  	if err != nil {
   115  		return errors.Trace(err)
   116  	}
   117  	w.conf.AddScripts(toolsDownloadCmds...)
   118  
   119  	w.conf.AddScripts(
   120  		`$dToolsHash = Get-FileSHA256 -FilePath "$binDir\tools.tar.gz"`,
   121  		fmt.Sprintf(`$dToolsHash > "$binDir\juju%s.sha256"`, tools.Version),
   122  		fmt.Sprintf(`if ($dToolsHash.ToLower() -ne "%s"){ Throw "Tools checksum mismatch"}`,
   123  			tools.SHA256),
   124  		fmt.Sprintf(`GUnZip-File -infile $binDir\tools.tar.gz -outdir $binDir`),
   125  		`rm "$binDir\tools.tar*"`,
   126  		fmt.Sprintf(`Set-Content $binDir\downloaded-tools.txt '%s'`, string(toolsJson)),
   127  	)
   128  
   129  	for _, cmd := range createJujuRegistryKeyCmds(w.icfg.Series) {
   130  		w.conf.AddRunCmd(cmd)
   131  	}
   132  
   133  	machineTag := names.NewMachineTag(w.icfg.MachineId)
   134  	_, err = w.addAgentInfo(machineTag)
   135  	if err != nil {
   136  		return errors.Trace(err)
   137  	}
   138  	return w.addMachineAgentToBoot()
   139  }
   140  
   141  // createJujuRegistryKeyCmds is going to create a juju registry key and set
   142  // permissions on it such that it's only accessible to administrators
   143  func createJujuRegistryKeyCmds(series string) []string {
   144  	aclCmds := setACLs(osenv.JujuRegistryKey, registryEntry, series)
   145  	regCmds := []string{
   146  
   147  		// Create a registry key for storing juju related information
   148  		fmt.Sprintf(`New-Item -Path '%s'`, osenv.JujuRegistryKey),
   149  
   150  		// Create a JUJU_DEV_FEATURE_FLAGS entry which may or may not be empty.
   151  		fmt.Sprintf(`New-ItemProperty -Path '%s' -Name '%s'`,
   152  			osenv.JujuRegistryKey,
   153  			osenv.JujuFeatureFlagEnvKey),
   154  		fmt.Sprintf(`Set-ItemProperty -Path '%s' -Name '%s' -Value '%s'`,
   155  			osenv.JujuRegistryKey,
   156  			osenv.JujuFeatureFlagEnvKey,
   157  			featureflag.AsEnvironmentValue()),
   158  	}
   159  	return append(regCmds[:1], append(aclCmds, regCmds[1:]...)...)
   160  }
   161  
   162  func setACLs(path string, permType aclType, ser string) []string {
   163  	ruleModel := `$rule = New-Object System.Security.AccessControl.%sAccessRule %s`
   164  	permModel := `%s = "%s", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"`
   165  	adminPermVar := `$adminPerm`
   166  	jujudPermVar := `$jujudPerm`
   167  
   168  	rulesToAdd := []string{
   169  		// $adminsGroup must be defined before calling setACLs
   170  		fmt.Sprintf(permModel, adminPermVar, `$adminsGroup`),
   171  		fmt.Sprintf(ruleModel, permType, adminPermVar),
   172  		`$acl.AddAccessRule($rule)`,
   173  	}
   174  
   175  	if !series.IsWindowsNano(ser) {
   176  		jujudUserACLRules := []string{
   177  			fmt.Sprintf(permModel, jujudPermVar, `jujud`),
   178  			fmt.Sprintf(ruleModel, permType, jujudPermVar),
   179  			`$acl.AddAccessRule($rule)`,
   180  		}
   181  
   182  		rulesToAdd = append(rulesToAdd, jujudUserACLRules...)
   183  	}
   184  
   185  	aclCmds := []string{
   186  		fmt.Sprintf(`$acl = Get-Acl -Path '%s'`, path),
   187  
   188  		// Reset the ACL's on it and add administrator access only.
   189  		`$acl.SetAccessRuleProtection($true, $false)`,
   190  
   191  		fmt.Sprintf(`Set-Acl -Path '%s' -AclObject $acl`, path),
   192  	}
   193  
   194  	return append(aclCmds[:2], append(rulesToAdd, aclCmds[2:]...)...)
   195  }
   196  
   197  func addDownloadToolsCmds(ser string, certificate string, toolsList tools.List) ([]string, error) {
   198  	var cmds []string
   199  	var getDownloadFileCmd func(url string) string
   200  	if series.IsWindowsNano(ser) {
   201  		parsedCert, err := cert.ParseCert(certificate)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		caCert := base64.StdEncoding.EncodeToString(parsedCert.Raw)
   206  		cmds = []string{fmt.Sprintf(`$cacert = "%s"`, caCert),
   207  			`$cert_bytes = $cacert | %{ ,[System.Text.Encoding]::UTF8.GetBytes($_) }`,
   208  			`$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2(,$cert_bytes)`,
   209  			`$store = Get-Item Cert:\LocalMachine\AuthRoot`,
   210  			`$store.Open("ReadWrite")`,
   211  			`$store.Add($cert)`,
   212  		}
   213  		getDownloadFileCmd = func(url string) string {
   214  			return fmt.Sprintf(`Invoke-FastWebRequest -URI '%s' -OutFile "$binDir\tools.tar.gz"`, url)
   215  		}
   216  	} else {
   217  		cmds = []string{
   218  			`$WebClient = New-Object System.Net.WebClient`,
   219  			`[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}`,
   220  			`[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12`,
   221  		}
   222  		getDownloadFileCmd = func(url string) string {
   223  			return fmt.Sprintf(`$WebClient.DownloadFile('%s', "$binDir\tools.tar.gz");`, url)
   224  		}
   225  	}
   226  
   227  	// Attempt all of the URLs, one after the other, until one succeeds.
   228  	// If all of the URLs fail, we retry the whole lot. We retry in this
   229  	// way, rather than retrying individually, to avoid one permanently
   230  	// bad URL from holding up the download.
   231  	downloadCmds := make([]string, len(toolsList))
   232  	for i, tools := range toolsList {
   233  		downloadCmds[i] = fmt.Sprintf("{ %s }", getDownloadFileCmd(tools.URL))
   234  	}
   235  	downloadCmd := fmt.Sprintf("ExecRetry { TryExecAll @(%s) }", strings.Join(downloadCmds, ", "))
   236  	cmds = append(cmds, downloadCmd)
   237  
   238  	return cmds, nil
   239  }