Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
243 lines (204 sloc) 8.04 KB
// Copyright 2012, 2013, 2014, 2015 Canonical Ltd.
// Copyright 2014, 2015 Cloudbase Solutions SRL
// Licensed under the AGPLv3, see LICENCE file for details.
package cloudconfig
import (
"encoding/base64"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"github.com/juju/errors"
"github.com/juju/utils/cert"
"github.com/juju/utils/featureflag"
"github.com/juju/utils/series"
"gopkg.in/juju/names.v2"
"github.com/juju/juju/juju/osenv"
"github.com/juju/juju/juju/paths"
"github.com/juju/juju/tools"
)
//go:generate go run ../generate/filetoconst/filetoconst.go UserDataScript windowsuserdatafiles/userdata.ps1 winuserdatawrapper.go 2016 cloudconfig
//go:generate go run ../generate/winuserdata/winuserdata.go 2016 winuserdata.go cloudconfig
type aclType string
const (
fileSystem aclType = "FileSystem"
registryEntry aclType = "Registry"
)
type windowsConfigure struct {
baseConfigure
}
// Configure updates the provided cloudinit.Config with
// configuration to initialize a Juju machine agent.
func (w *windowsConfigure) Configure() error {
if err := w.ConfigureBasic(); err != nil {
return err
}
return w.ConfigureJuju()
}
func (w *windowsConfigure) ConfigureBasic() error {
tmpDir, err := paths.TempDir(w.icfg.Series)
if err != nil {
return err
}
renderer := w.conf.ShellRenderer()
dataDir := renderer.FromSlash(w.icfg.DataDir)
baseDir := renderer.FromSlash(filepath.Dir(tmpDir))
binDir := renderer.Join(baseDir, "bin")
w.conf.AddScripts(windowsPowershellHelpers)
// The jujud user only gets created on non-nano versions for now.
if !series.IsWindowsNano(w.icfg.Series) {
w.conf.AddScripts(addJujuUser)
}
w.conf.AddScripts(
// Some providers create a baseDir before this step, but we need to
// make sure it exists before applying icacls
fmt.Sprintf(`mkdir -Force "%s"`, renderer.FromSlash(baseDir)),
fmt.Sprintf(`mkdir %s`, renderer.FromSlash(tmpDir)),
fmt.Sprintf(`mkdir "%s"`, binDir),
fmt.Sprintf(`mkdir "%s\locks"`, renderer.FromSlash(dataDir)),
`setx /m PATH "$env:PATH;C:\Juju\bin\"`,
// This is necessary for setACLs to work
`$adminsGroup = (New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount])`,
fmt.Sprintf(`icacls "%s" /inheritance:r /grant "${adminsGroup}:(OI)(CI)(F)" /t`, renderer.FromSlash(baseDir)),
)
// TODO(bogdanteleaga): This, together with the call above, should be using setACLs, once it starts working across all windows versions properly.
// Until then, if we change permissions, both this and setACLs should be changed to do the same thing.
if !series.IsWindowsNano(w.icfg.Series) {
w.conf.AddScripts(fmt.Sprintf(`icacls "%s" /inheritance:r /grant "jujud:(OI)(CI)(F)" /t`, renderer.FromSlash(baseDir)))
}
noncefile := renderer.Join(dataDir, NonceFile)
w.conf.AddScripts(
fmt.Sprintf(`Set-Content "%s" "%s"`, noncefile, shquote(w.icfg.MachineNonce)),
)
return nil
}
func (w *windowsConfigure) ConfigureJuju() error {
if err := w.icfg.VerifyConfig(); err != nil {
return errors.Trace(err)
}
if w.icfg.Controller != nil {
return errors.Errorf("controllers not supported on windows")
}
tools := w.icfg.ToolsList()[0]
toolsJson, err := json.Marshal(tools)
if err != nil {
return errors.Annotate(err, "while serializing the tools")
}
renderer := w.conf.ShellRenderer()
w.conf.AddScripts(
fmt.Sprintf(`$binDir="%s"`, renderer.FromSlash(w.icfg.JujuTools())),
fmt.Sprintf(`mkdir '%s'`, renderer.FromSlash(w.icfg.LogDir)),
`mkdir $binDir`,
)
toolsDownloadCmds, err := addDownloadToolsCmds(
w.icfg.Series, w.icfg.APIInfo.CACert, w.icfg.ToolsList(),
)
if err != nil {
return errors.Trace(err)
}
w.conf.AddScripts(toolsDownloadCmds...)
w.conf.AddScripts(
`$dToolsHash = Get-FileSHA256 -FilePath "$binDir\tools.tar.gz"`,
fmt.Sprintf(`$dToolsHash > "$binDir\juju%s.sha256"`, tools.Version),
fmt.Sprintf(`if ($dToolsHash.ToLower() -ne "%s"){ Throw "Tools checksum mismatch"}`,
tools.SHA256),
fmt.Sprintf(`GUnZip-File -infile $binDir\tools.tar.gz -outdir $binDir`),
`rm "$binDir\tools.tar*"`,
fmt.Sprintf(`Set-Content $binDir\downloaded-tools.txt '%s'`, string(toolsJson)),
)
for _, cmd := range createJujuRegistryKeyCmds(w.icfg.Series) {
w.conf.AddRunCmd(cmd)
}
machineTag := names.NewMachineTag(w.icfg.MachineId)
_, err = w.addAgentInfo(machineTag)
if err != nil {
return errors.Trace(err)
}
return w.addMachineAgentToBoot()
}
// createJujuRegistryKeyCmds is going to create a juju registry key and set
// permissions on it such that it's only accessible to administrators
func createJujuRegistryKeyCmds(series string) []string {
aclCmds := setACLs(osenv.JujuRegistryKey, registryEntry, series)
regCmds := []string{
// Create a registry key for storing juju related information
fmt.Sprintf(`New-Item -Path '%s'`, osenv.JujuRegistryKey),
// Create a JUJU_DEV_FEATURE_FLAGS entry which may or may not be empty.
fmt.Sprintf(`New-ItemProperty -Path '%s' -Name '%s'`,
osenv.JujuRegistryKey,
osenv.JujuFeatureFlagEnvKey),
fmt.Sprintf(`Set-ItemProperty -Path '%s' -Name '%s' -Value '%s'`,
osenv.JujuRegistryKey,
osenv.JujuFeatureFlagEnvKey,
featureflag.AsEnvironmentValue()),
}
return append(regCmds[:1], append(aclCmds, regCmds[1:]...)...)
}
func setACLs(path string, permType aclType, ser string) []string {
ruleModel := `$rule = New-Object System.Security.AccessControl.%sAccessRule %s`
permModel := `%s = "%s", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"`
adminPermVar := `$adminPerm`
jujudPermVar := `$jujudPerm`
rulesToAdd := []string{
// $adminsGroup must be defined before calling setACLs
fmt.Sprintf(permModel, adminPermVar, `$adminsGroup`),
fmt.Sprintf(ruleModel, permType, adminPermVar),
`$acl.AddAccessRule($rule)`,
}
if !series.IsWindowsNano(ser) {
jujudUserACLRules := []string{
fmt.Sprintf(permModel, jujudPermVar, `jujud`),
fmt.Sprintf(ruleModel, permType, jujudPermVar),
`$acl.AddAccessRule($rule)`,
}
rulesToAdd = append(rulesToAdd, jujudUserACLRules...)
}
aclCmds := []string{
fmt.Sprintf(`$acl = Get-Acl -Path '%s'`, path),
// Reset the ACL's on it and add administrator access only.
`$acl.SetAccessRuleProtection($true, $false)`,
fmt.Sprintf(`Set-Acl -Path '%s' -AclObject $acl`, path),
}
return append(aclCmds[:2], append(rulesToAdd, aclCmds[2:]...)...)
}
func addDownloadToolsCmds(ser string, certificate string, toolsList tools.List) ([]string, error) {
var cmds []string
var getDownloadFileCmd func(url string) string
if series.IsWindowsNano(ser) {
parsedCert, err := cert.ParseCert(certificate)
if err != nil {
return nil, err
}
caCert := base64.StdEncoding.EncodeToString(parsedCert.Raw)
cmds = []string{fmt.Sprintf(`$cacert = "%s"`, caCert),
`$cert_bytes = $cacert | %{ ,[System.Text.Encoding]::UTF8.GetBytes($_) }`,
`$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2(,$cert_bytes)`,
`$store = Get-Item Cert:\LocalMachine\AuthRoot`,
`$store.Open("ReadWrite")`,
`$store.Add($cert)`,
}
getDownloadFileCmd = func(url string) string {
return fmt.Sprintf(`Invoke-FastWebRequest -URI '%s' -OutFile "$binDir\tools.tar.gz"`, url)
}
} else {
cmds = []string{
`$WebClient = New-Object System.Net.WebClient`,
`[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}`,
`[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12`,
}
getDownloadFileCmd = func(url string) string {
return fmt.Sprintf(`$WebClient.DownloadFile('%s', "$binDir\tools.tar.gz");`, url)
}
}
// Attempt all of the URLs, one after the other, until one succeeds.
// If all of the URLs fail, we retry the whole lot. We retry in this
// way, rather than retrying individually, to avoid one permanently
// bad URL from holding up the download.
downloadCmds := make([]string, len(toolsList))
for i, tools := range toolsList {
downloadCmds[i] = fmt.Sprintf("{ %s }", getDownloadFileCmd(tools.URL))
}
downloadCmd := fmt.Sprintf("ExecRetry { TryExecAll @(%s) }", strings.Join(downloadCmds, ", "))
cmds = append(cmds, downloadCmd)
return cmds, nil
}