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