[BUG] salt-cloud naively assumes winrm commands succeed (and so deployments fail silently)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 15.7k
- Forks
- 5.6k
- Avg merge
- 2d 44m
- Merged PRs (30d)
- 80
Description
Description
Using salt-cloud 3004 to deploy windows 2016 in EC2 and deployment always fails to deploy the minion but the job succeeds.
Aside: I haven't figured out why the minion fails to deploy yet but this issue is more about the lack of debuggability when it does
Setup
general setup instructions/script
read -p "Subnet ID? " SUBNETID
read -p "Security Group ID" SECURITY_GROUP_ID
# cat <<EOF > /etc/salt/cloud.providers.d/ec2.conf
ec2:
id: 'use-instance-role-credentials'
key: 'use-instance-role-credentials'
keyname: se-demo-aws
# securitygroup: default
private_key: /etc/salt/pki/cloud/mykey.pem
location: us-west-2
ssh_interface: private_ips
del_root_vol_on_destroy: True
driver: ec2
sync_after_install: all
EOF
cat <<EOF > /etc/salt/cloud.profiles.d/ec2.conf
ec2-w2016-us-west-2:
provider: ec2
size: t2.large
userdata_file: /etc/salt/tools/windows-firewall.ps1
location: us-west-2
image: "Windows_Server-2016-English-Core-Base-2022.02.10"
private_key: /etc/salt/pki/cloud/mykey.pem
use_winrm: true
winrm_verify_ssl: false
win_username: Administrator
win_password: auto
win_installer: /srv/minion-cache/Salt-Minion-3004-Py3-AMD64-Setup.exe
smb_port: 445
network_interfaces:
- DeviceIndex: 0
PrivateIpAddresses:
- Primary: True
#auto assign public ip (not EIP)
AssociatePublicIpAddress: True
SubnetId: $SUBNET_ID
SecurityGroupId:
- $SECURITY_GROUP_ID
EOF
mkdir -p /etc/salt/tools
echo "Stolen from https://docs.saltproject.io/en/latest/topics/cloud/windows.html"
cat <<EOF > /etc/salt/tools/windows-firewall.ps1
<powershell>
New-NetFirewallRule -Name "SMB445" -DisplayName "SMB445" -Protocol TCP -LocalPort 445
New-NetFirewallRule -Name "WINRM5986" -DisplayName "WINRM5986" -Protocol TCP -LocalPort 5986
winrm quickconfig -q
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}'
winrm set winrm/config '@{MaxTimeoutms="1800000"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
$SourceStoreScope = 'LocalMachine'
$SourceStorename = 'Remote Desktop'
$SourceStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $SourceStorename, $SourceStoreScope
$SourceStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
$cert = $SourceStore.Certificates | Where-Object -FilterScript {
$_.subject -like '*'
}
$DestStoreScope = 'LocalMachine'
$DestStoreName = 'My'
$DestStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $DestStoreName, $DestStoreScope
$DestStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$DestStore.Add($cert)
$SourceStore.Close()
$DestStore.Close()
winrm create winrm/config/listener?Address=*+Transport=HTTPS `@`{CertificateThumbprint=`"($cert.Thumbprint)`"`}
Restart-Service winrm
</powershell>
EOF
Steps to Reproduce the behavior
Using the above config (with pip installed smbprotocol and winrm)...
# salt-cloud -p ec2-w2016-us-west-2 dkf-sse860-01-w2016-01 -l debug 2>&1 | tee /tmp/salt-cloud-w2016.log
This succeeds in deploying the instance but it fails to install the minion. This is where it goes wrong...
pdb debug of the build
> /usr/lib/python3.6/site-packages/salt/utils/cloud.py(1334)deploy_windows()
-> if use_winrm:
(Pdb) l
1329 "C$",
1330 conn=smb_conn,
1331 )
1332 import pdb
1333 pdb.set_trace()
1334 -> if use_winrm:
1335 winrm_cmd(
1336 winrm_session,
1337 "c:\\salttemp\\{}".format(installer),
1338 ["/S", "/master={}".format(master), "/minion-name={}".format(name)],
1339 )
(Pdb) n
> /usr/lib/python3.6/site-packages/salt/utils/cloud.py(1335)deploy_windows()
-> winrm_cmd(
(Pdb) s
> /usr/lib/python3.6/site-packages/salt/utils/cloud.py(1336)deploy_windows()
-> winrm_session,
(Pdb) s
> /usr/lib/python3.6/site-packages/salt/utils/cloud.py(1337)deploy_windows()
-> "c:\\salttemp\\{}".format(installer),
(Pdb) s
> /usr/lib/python3.6/site-packages/salt/utils/cloud.py(1338)deploy_windows()
-> ["/S", "/master={}".format(master), "/minion-name={}".format(name)],
(Pdb) s
--Call--
> /usr/lib/python3.6/site-packages/salt/utils/cloud.py(2399)winrm_cmd()
-> def winrm_cmd(session, command, flags, **kwargs):
(Pdb) l
2394 log.exception("Failed to execute command '%s'", logging_command)
2395 # Signal an error
2396 return 1
2397
2398
2399 -> def winrm_cmd(session, command, flags, **kwargs):
2400 """
2401 Wrapper for commands to be run against Windows boxes using WinRM.
2402 """
2403 log.debug("Executing WinRM command: %s %s", command, flags)
2404 r = session.run_cmd(command, flags)
(Pdb) n
> /usr/lib/python3.6/site-packages/salt/utils/cloud.py(2403)winrm_cmd()
-> log.debug("Executing WinRM command: %s %s", command, flags)
(Pdb) n
[DEBUG ] Executing WinRM command: c:\salttemp\Salt-Minion-3004-Py3-AMD64-Setup.exe ['/S', '/master=ip-10-0-195-179.us-west-2.compute.internal', '/minion-name=dkf-sse860-01-w2016-01']
> /usr/lib/python3.6/site-packages/salt/utils/cloud.py(2404)winrm_cmd()
-> r = session.run_cmd(command, flags)
(Pdb) s
--Call--
> /usr/local/lib/python3.6/site-packages/winrm/__init__.py(37)run_cmd()
-> def run_cmd(self, command, args=()):
(Pdb) l
32 username, password = auth
33 self.url = self._build_url(target, kwargs.get('transport', 'plaintext'))
34 self.protocol = Protocol(self.url,
35 username=username, password=password, **kwargs)
36
37 -> def run_cmd(self, command, args=()):
38 # TODO optimize perf. Do not call open/close shell every time
39 shell_id = self.protocol.open_shell()
40 command_id = self.protocol.run_command(shell_id, command, args)
41 rs = Response(self.protocol.get_command_output(shell_id, command_id))
42 self.protocol.cleanup_command(shell_id, command_id)
(Pdb) n
> /usr/local/lib/python3.6/site-packages/winrm/__init__.py(39)run_cmd()
-> shell_id = self.protocol.open_shell()
(Pdb) n
[DEBUG ] Starting new HTTPS connection (1): 10.0.110.7:5986
[DEBUG ] https://10.0.110.7:5986 "POST /wsman HTTP/1.1" 200 1628
> /usr/local/lib/python3.6/site-packages/winrm/__init__.py(40)run_cmd()
-> command_id = self.protocol.run_command(shell_id, command, args)
(Pdb) n
[DEBUG ] https://10.0.110.7:5986 "POST /wsman HTTP/1.1" 200 847
> /usr/local/lib/python3.6/site-packages/winrm/__init__.py(41)run_cmd()
-> rs = Response(self.protocol.get_command_output(shell_id, command_id))
(Pdb) n
[DEBUG ] https://10.0.110.7:5986 "POST /wsman HTTP/1.1" 500 1320
[DEBUG ] https://10.0.110.7:5986 "POST /wsman HTTP/1.1" 500 1320
[DEBUG ] https://10.0.110.7:5986 "POST /wsman HTTP/1.1" 200 1119
> /usr/local/lib/python3.6/site-packages/winrm/__init__.py(42)run_cmd()
-> self.protocol.cleanup_command(shell_id, command_id)
Weirdly enough, I've done this a few times and when I step over the rs = Response() I get the http error 500s. But if I step into that call, it retursn a bunch of http 200's (timing issue?) but the minion doesn't install correctly.
Anyway, there's no error checking going on here and no output is available for us to debug.
Expected behavior
It should fail and tell users why, without them having to resort to pdb to figure it out.
Versions Report
salt --versions-report
(Provided by running salt --versions-report. Please also mention any differences in master/minion versions.)# salt-cloud --versions
Salt Version:
Salt: 3004
Dependency Versions:
Apache Libcloud: 3.2.0
cffi: 1.9.1
cherrypy: 5.6.0
dateutil: Not Installed
docker-py: Not Installed
gitdb: 0.6.4
gitpython: 1.0.1
Jinja2: 2.11.1
libgit2: Not Installed
M2Crypto: 0.35.2
Mako: Not Installed
msgpack: 0.6.2
msgpack-pure: Not Installed
mysql-python: Not Installed
pycparser: 2.14
pycrypto: Not Installed
pycryptodome: 3.11.0
pygit2: Not Installed
Python: 3.6.8 (default, Nov 16 2020, 16:55:22)
python-gnupg: Not Installed
PyYAML: 3.13
PyZMQ: 17.0.0
smmap: 0.9.0
timelib: Not Installed
Tornado: 4.5.3
ZMQ: 4.1.4
Salt Extensions:
SSEAPE: 8.6.0.3
System Versions:
dist: centos 7 Core
locale: UTF-8
machine: x86_64
release: 3.10.0-1160.53.1.el7.x86_64
system: Linux
version: CentOS Linux 7 Core
Additional context
Add any other context about the problem here.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in salt/utils/cloud.py at deploy_windows() and winrm_cmd(), then reproduce with the supplied salt-cloud command and debug logging. Trace how session.run_cmd() handles the WinRM response and compare the command result with the deployment outcome. Done means a failed Windows minion installation reports the WinRM failure or command output instead of appearing successful.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, python
- Domain
- cloud, infrastructure
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100