forked from juju/juju
-
Notifications
You must be signed in to change notification settings - Fork 0
/
userdatacfg_win.go
242 lines (204 loc) · 8.04 KB
/
userdatacfg_win.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// 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/featureflag"
"github.com/juju/utils/series"
"gopkg.in/juju/names.v2"
"github.com/juju/juju/cert"
"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
}