winrm-s 0.1.0.rc.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +5 -0
- data/Gemfile +8 -0
- data/LICENSE +201 -0
- data/README.md +69 -0
- data/Rakefile +1 -0
- data/lib/winrm-s.rb +30 -0
- data/lib/winrm-s/version.rb +22 -0
- data/lib/winrm/helpers/assert_patch.rb +20 -0
- data/lib/winrm/http/auth.rb +171 -0
- data/lib/winrm/http/transport_patch.rb +35 -0
- data/lib/winrm/win32/sspi.rb +256 -0
- data/lib/winrm/winrm_service_patch.rb +34 -0
- data/spec/functional/sspi_nego_encrypt_decrypt_spec.rb +65 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/unit/platform_spec.rb +28 -0
- data/winrm-s.gemspec +25 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MDRmZmUzNTQ4MDI3ZDc4YjY4MDcwMTk4NTY4ZWY3MmUzYTAwY2M5Nw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YzgwZjhlN2UxNTZmNThlOGNjMWVkNDZhZjFhOGZjMTg5NzI3ODhmZQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NzAxNzMwNDE5ODIwZDQ1ZWVhMWI2MTE3MjZmNjNhOWNmYjE5MTgwNGI2OWQx
|
10
|
+
MzY0MjlhN2JlNDVjMDY3YzFlNTdlMjAzOWQ0Mzg0OTY0ZGMyZmE2NTg1ODJh
|
11
|
+
MmUyNmI0M2EyMzZkNTlhNjIzMWU5ZmQ0ZDVmNjI0NmU1ZWYxOTA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MGM5NmI4ZTdhZjVkYzk2OTFhYWNiMmNlMzMwYjkyN2JhZmY2MDcwNjM3MmFm
|
14
|
+
MGU2NWU2Y2MzOWZjNWI1M2M2ZWFhMzFhNzYyN2Q1M2JkNTkzMGMzMmFkZWE0
|
15
|
+
OTUxY2M3ZTE5YjllZjhkNjhkYWQ1ODY5ZjgwN2UyNzU2MWI1NTA=
|
data/.gitignore
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
.autotest
|
2
|
+
coverage
|
3
|
+
.DS_Store
|
4
|
+
pkg
|
5
|
+
*/tags
|
6
|
+
*~
|
7
|
+
|
8
|
+
# you should check in your Gemfile.lock in applications, and not in gems
|
9
|
+
Gemfile.lock
|
10
|
+
Gemfile.local
|
11
|
+
|
12
|
+
# Do not check in the .bundle directory, or any of the files inside it. Those files are specific to each particular machine, and are used to persist installation options between runs of the bundle install command.
|
13
|
+
.bundle
|
14
|
+
|
15
|
+
# ignore some common Bundler 'binstubs' directory names
|
16
|
+
# http://gembundler.com/man/bundle-exec.1.html
|
17
|
+
b/
|
18
|
+
binstubs/
|
19
|
+
|
20
|
+
# RVM and RBENV ruby version files
|
21
|
+
.rbenv-version
|
22
|
+
.rvmrc
|
23
|
+
|
24
|
+
# Documentation
|
25
|
+
_site/*
|
26
|
+
.yardoc/
|
27
|
+
doc/
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
Apache License
|
2
|
+
Version 2.0, January 2004
|
3
|
+
http://www.apache.org/licenses/
|
4
|
+
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
6
|
+
|
7
|
+
1. Definitions.
|
8
|
+
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
11
|
+
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
13
|
+
the copyright owner that is granting the License.
|
14
|
+
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
16
|
+
other entities that control, are controlled by, or are under common
|
17
|
+
control with that entity. For the purposes of this definition,
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
19
|
+
direction or management of such entity, whether by contract or
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
22
|
+
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
24
|
+
exercising permissions granted by this License.
|
25
|
+
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
27
|
+
including but not limited to software source code, documentation
|
28
|
+
source, and configuration files.
|
29
|
+
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
31
|
+
transformation or translation of a Source form, including but
|
32
|
+
not limited to compiled object code, generated documentation,
|
33
|
+
and conversions to other media types.
|
34
|
+
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
36
|
+
Object form, made available under the License, as indicated by a
|
37
|
+
copyright notice that is included in or attached to the work
|
38
|
+
(an example is provided in the Appendix below).
|
39
|
+
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
46
|
+
the Work and Derivative Works thereof.
|
47
|
+
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
49
|
+
the original version of the Work and any modifications or additions
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
61
|
+
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
64
|
+
subsequently incorporated within the Work.
|
65
|
+
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
72
|
+
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
78
|
+
where such license applies only to those patent claims licensable
|
79
|
+
by such Contributor that are necessarily infringed by their
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
82
|
+
institute patent litigation against any entity (including a
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
85
|
+
or contributory patent infringement, then any patent licenses
|
86
|
+
granted to You under this License for that Work shall terminate
|
87
|
+
as of the date such litigation is filed.
|
88
|
+
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
91
|
+
modifications, and in Source or Object form, provided that You
|
92
|
+
meet the following conditions:
|
93
|
+
|
94
|
+
(a) You must give any other recipients of the Work or
|
95
|
+
Derivative Works a copy of this License; and
|
96
|
+
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
98
|
+
stating that You changed the files; and
|
99
|
+
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
102
|
+
attribution notices from the Source form of the Work,
|
103
|
+
excluding those notices that do not pertain to any part of
|
104
|
+
the Derivative Works; and
|
105
|
+
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
108
|
+
include a readable copy of the attribution notices contained
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
111
|
+
of the following places: within a NOTICE text file distributed
|
112
|
+
as part of the Derivative Works; within the Source form or
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
114
|
+
within a display generated by the Derivative Works, if and
|
115
|
+
wherever such third-party notices normally appear. The contents
|
116
|
+
of the NOTICE file are for informational purposes only and
|
117
|
+
do not modify the License. You may add Your own attribution
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
120
|
+
that such additional attribution notices cannot be construed
|
121
|
+
as modifying the License.
|
122
|
+
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
124
|
+
may provide additional or different license terms and conditions
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
128
|
+
the conditions stated in this License.
|
129
|
+
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
133
|
+
this License, without any additional terms or conditions.
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
135
|
+
the terms of any separate license agreement you may have executed
|
136
|
+
with Licensor regarding such Contributions.
|
137
|
+
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
140
|
+
except as required for reasonable and customary use in describing the
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
142
|
+
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
152
|
+
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
158
|
+
incidental, or consequential damages of any character arising as a
|
159
|
+
result of this License or out of the use or inability to use the
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
162
|
+
other commercial damages or losses), even if such Contributor
|
163
|
+
has been advised of the possibility of such damages.
|
164
|
+
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
168
|
+
or other liability obligations and/or rights consistent with this
|
169
|
+
License. However, in accepting such obligations, You may act only
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
174
|
+
of your accepting any such warranty or additional liability.
|
175
|
+
|
176
|
+
END OF TERMS AND CONDITIONS
|
177
|
+
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
179
|
+
|
180
|
+
To apply the Apache License to your work, attach the following
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
182
|
+
replaced with your own identifying information. (Don't include
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
184
|
+
comment syntax for the file format. We also recommend that a
|
185
|
+
file or class name and description of purpose be included on the
|
186
|
+
same "printed page" as the copyright notice for easier
|
187
|
+
identification within third-party archives.
|
188
|
+
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
190
|
+
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
192
|
+
you may not use this file except in compliance with the License.
|
193
|
+
You may obtain a copy of the License at
|
194
|
+
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
196
|
+
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200
|
+
See the License for the specific language governing permissions and
|
201
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
winrm-s
|
2
|
+
=======
|
3
|
+
|
4
|
+
`winrm-s` extends the functionality of the
|
5
|
+
[WinRM gem](http://rubygems.org/gems/winrm) to support the Microsoft
|
6
|
+
[Negotiate protocol](http://msdn.microsoft.com/en-us/library/windows/desktop/aa378748(v=vs.85).aspx)
|
7
|
+
when authenticating to a remote [WinRM](http://msdn.microsoft.com/en-us/library/aa384426(v=vs.85).aspx) endpoint from a Windows system.
|
8
|
+
|
9
|
+
This extended functionality is **only** supported when running on Microsoft
|
10
|
+
Windows. This gem can still be used on other operating systems just like the
|
11
|
+
`WinRM` gem, but the extended capabilities will not be available.
|
12
|
+
|
13
|
+
Installation
|
14
|
+
------------
|
15
|
+
|
16
|
+
To install it, run:
|
17
|
+
|
18
|
+
gem install winrm-s
|
19
|
+
|
20
|
+
Usage
|
21
|
+
-----
|
22
|
+
|
23
|
+
`winrm-s` provides the same interface as the `winrm` gem -- see `winrm`
|
24
|
+
[documentation](https://github.com/WinRb/WinRM/blob/master/README.md) for `winrm-s` usage.
|
25
|
+
|
26
|
+
* To use it, simply require `winrm` or `winrm-s`, depending on whether your code
|
27
|
+
is running on Windows. The extended negotiate protocol is only available if
|
28
|
+
you include `winrm-s`, which will only work on Windows.
|
29
|
+
* When you use WinRM::WinRMWebService.new, be sure to specify the
|
30
|
+
`:sspinegotiate` parameter, along with a user name in the form `domain_name\user_name`
|
31
|
+
for the user name in order to make use of negotiate protocol. If the user
|
32
|
+
account is local to the remote system, you can use `.` for the domain. The
|
33
|
+
example further on demonstrates the negotiate use case.
|
34
|
+
* All other use cases enabled by the `winrm` gem are also supported.
|
35
|
+
|
36
|
+
Example
|
37
|
+
-------
|
38
|
+
Note the argument value of `:sspinegotiate` for transport option, and the
|
39
|
+
explicit specification of a domain name, in this case `.`, in the user name:
|
40
|
+
```ruby
|
41
|
+
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
|
42
|
+
require 'winrm-s' # only works on Windows, otherwise use require 'winrm'
|
43
|
+
endpoint = http://mywinrmhost:5985/wsman
|
44
|
+
winrm = WinRM::WinRMWebService.new(endpoint, :sspinegotiate, :user => ".\administrator", :pass => "adminpasswd")
|
45
|
+
winrm.cmd('ipconfig /all') do |stdout, stderr|
|
46
|
+
STDOUT.print stdout
|
47
|
+
STDERR.print stderr
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
License
|
53
|
+
-------
|
54
|
+
|
55
|
+
Copyright:: Copyright (c) 2014 Chef Software, Inc.
|
56
|
+
License:: Apache License, Version 2.0
|
57
|
+
|
58
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
59
|
+
you may not use this file except in compliance with the License.
|
60
|
+
You may obtain a copy of the License at
|
61
|
+
|
62
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
63
|
+
|
64
|
+
Unless required by applicable law or agreed to in writing, software
|
65
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
66
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
67
|
+
See the License for the specific language governing permissions and
|
68
|
+
limitations under the License.
|
69
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/lib/winrm-s.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)>
|
3
|
+
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'winrm'
|
20
|
+
|
21
|
+
def windows?
|
22
|
+
!!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Patch only on windows
|
26
|
+
if windows?
|
27
|
+
require 'winrm/winrm_service_patch'
|
28
|
+
else
|
29
|
+
raise "ERROR: winrm-s extensions to the winrm gem for the negotiate protocol are only supported on Windows. Require 'winrm' and not 'winrm-s' on non-Windows platforms."
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
|
3
|
+
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module WinrmS
|
20
|
+
VERSION = "0.1.0.rc.0"
|
21
|
+
MAJOR, MINOR, TINY = VERSION.split('.')
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
module PatchAssertions
|
3
|
+
|
4
|
+
def self.assert_major_version(library_name, expected_major, override_var_name)
|
5
|
+
supported_major_ver = ENV[override_var_name] || expected_major
|
6
|
+
loaded_ver = Gem.loaded_specs[library_name].version
|
7
|
+
loaded_major_ver = loaded_ver.to_s.match(/(^\d+\.\d+)\.(.*)$/)[1]
|
8
|
+
if loaded_major_ver.to_s != supported_major_ver.to_s
|
9
|
+
puts "Unsupported version of #{library_name}. The supported major version of library is #{library_name} version #{expected_major}. This code path monkey patches few methods in #{library_name} to support additional features. If you are aware of the impact of using #{loaded_ver}, this can be enabled by setting #{override_var_name} to the major version #{loaded_major_ver}"
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.assert_arity_of_patched_method(klass, method_name, expected_arity)
|
15
|
+
if klass.instance_method(method_name).arity != expected_arity
|
16
|
+
puts "Cannot patch method #{klass}::#{method_name} since your latest gem seems to have different method definition that cannot be safely patched. Please use the supported version of patched gem."
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
|
3
|
+
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'httpclient/auth'
|
20
|
+
|
21
|
+
require 'winrm/helpers/assert_patch'
|
22
|
+
|
23
|
+
def validate_patch
|
24
|
+
# The code below patches the httpclient library to add support
|
25
|
+
# for encrypt/decrypt as described below.
|
26
|
+
# Add few restriction to make sure the patched methods are still
|
27
|
+
# available, but still give a way to consciously use later versions
|
28
|
+
PatchAssertions.assert_major_version("httpclient", 2.4, "USE_HTTPCLIENT_MAJOR")
|
29
|
+
PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_request", 1)
|
30
|
+
PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_response", 2)
|
31
|
+
PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "set", -1)
|
32
|
+
PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "get", 1)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Perform the patch validations
|
36
|
+
validate_patch
|
37
|
+
|
38
|
+
# Overrides the HTTPClient::WWWAuth code to add support for encryption/decryption
|
39
|
+
# of data sent during the NTLM auth over negotiate.
|
40
|
+
|
41
|
+
# Also Overrides HTTPClient::SSPINegotiateAuth to remember user credentials, original
|
42
|
+
# library code relies on the current login user credentials on the client machine.
|
43
|
+
|
44
|
+
# Below code helps ruby client to perform auth using the credentials provided to
|
45
|
+
# ruby client, and also enhances to use encrypted channel.
|
46
|
+
|
47
|
+
class HTTPClient
|
48
|
+
|
49
|
+
class WWWAuth
|
50
|
+
|
51
|
+
# Filter API implementation. Traps HTTP request and insert
|
52
|
+
# 'Authorization' header if needed.
|
53
|
+
def filter_request(req)
|
54
|
+
@authenticator.each do |auth|
|
55
|
+
next unless auth.set? # hasn't be set, don't use it
|
56
|
+
if cred = auth.get(req)
|
57
|
+
auth.encrypt_payload(req) if auth.respond_to?(:encrypt_payload)
|
58
|
+
req.header.set('Authorization', auth.scheme + " " + cred)
|
59
|
+
return
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Filter API implementation. Traps HTTP response and parses
|
65
|
+
# 'WWW-Authenticate' header.
|
66
|
+
#
|
67
|
+
# This remembers the challenges for all authentication methods
|
68
|
+
# available to the client. On the subsequent retry of the request,
|
69
|
+
# filter_request will select the strongest method.
|
70
|
+
def filter_response(req, res)
|
71
|
+
command = nil
|
72
|
+
if res.status == HTTP::Status::UNAUTHORIZED
|
73
|
+
if challenge = parse_authentication_header(res, 'www-authenticate')
|
74
|
+
uri = req.header.request_uri
|
75
|
+
challenge.each do |scheme, param_str|
|
76
|
+
@authenticator.each do |auth|
|
77
|
+
next unless auth.set? # hasn't be set, don't use it
|
78
|
+
if scheme.downcase == auth.scheme.downcase
|
79
|
+
challengeable = auth.challenge(uri, param_str)
|
80
|
+
command = :retry if challengeable
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
# ignore unknown authentication scheme
|
85
|
+
end
|
86
|
+
elsif res.status == HTTP::Status::OK
|
87
|
+
decrypted_content = res.content
|
88
|
+
@authenticator.each do |auth|
|
89
|
+
next unless auth.set? # hasn't be set, don't use it
|
90
|
+
decrypted_content = auth.decrypt_payload(res.content) if auth.respond_to?(:encrypted_channel?) and auth.encrypted_channel?
|
91
|
+
end
|
92
|
+
# update with decrypted content
|
93
|
+
res.content.replace(decrypted_content) if res.content and !res.content.empty?
|
94
|
+
end
|
95
|
+
command
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class SSPINegotiateAuth
|
100
|
+
# Override to remember creds
|
101
|
+
# Set authentication credential.
|
102
|
+
def set(uri, user, passwd)
|
103
|
+
# Check if user has domain specified in it.
|
104
|
+
if user
|
105
|
+
creds = user.split("\\")
|
106
|
+
creds.length.eql?(2) ? (@domain,@user = creds) : @user = creds[0]
|
107
|
+
end
|
108
|
+
@passwd = passwd
|
109
|
+
end
|
110
|
+
|
111
|
+
# Response handler: returns credential.
|
112
|
+
# See win32/sspi for negotiation state transition.
|
113
|
+
def get(req)
|
114
|
+
return nil unless SSPIEnabled || GSSAPIEnabled
|
115
|
+
target_uri = req.header.request_uri
|
116
|
+
domain_uri, param = @challenge.find { |uri, v|
|
117
|
+
Util.uri_part_of(target_uri, uri)
|
118
|
+
}
|
119
|
+
|
120
|
+
return nil unless param
|
121
|
+
state = param[:state]
|
122
|
+
authenticator = param[:authenticator]
|
123
|
+
authphrase = param[:authphrase]
|
124
|
+
case state
|
125
|
+
when :init
|
126
|
+
if SSPIEnabled
|
127
|
+
# Over-ride ruby win32 sspi to support encrypt/decrypt
|
128
|
+
require 'winrm/win32/sspi'
|
129
|
+
authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new(@user, @domain, @passwd)
|
130
|
+
@authenticator = authenticator # **** Hacky remember as we need this for encrypt/decrypt
|
131
|
+
return authenticator.get_initial_token
|
132
|
+
else # use GSSAPI
|
133
|
+
authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
|
134
|
+
# Base64 encode the context token
|
135
|
+
return [authenticator.init_context].pack('m').gsub(/\n/,'')
|
136
|
+
end
|
137
|
+
when :response
|
138
|
+
@challenge.delete(domain_uri)
|
139
|
+
if SSPIEnabled
|
140
|
+
return authenticator.complete_authentication(authphrase)
|
141
|
+
else # use GSSAPI
|
142
|
+
return authenticator.init_context(authphrase.unpack('m').pop)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
|
148
|
+
def encrypted_channel?
|
149
|
+
@encrypted_channel
|
150
|
+
end
|
151
|
+
|
152
|
+
def encrypt_payload(req)
|
153
|
+
if SSPIEnabled
|
154
|
+
body = @authenticator.encrypt_payload(req.body)
|
155
|
+
req.http_body = HTTP::Message::Body.new
|
156
|
+
req.http_body.init_request(body)
|
157
|
+
req.http_header.body_size = body.length if body
|
158
|
+
# if body is encrypted update the header
|
159
|
+
if body.include? "HTTP-SPNEGO-session-encrypted"
|
160
|
+
@encrypted_channel = true
|
161
|
+
req.header.set('Content-Type', "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\"")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def decrypt_payload(body)
|
167
|
+
body = @authenticator.decrypt_payload(body) if SSPIEnabled
|
168
|
+
body
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
|
3
|
+
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module WinRM
|
20
|
+
module HTTP
|
21
|
+
|
22
|
+
class HttpSSPINegotiate < HttpTransport
|
23
|
+
def initialize(endpoint, user, pass, opts)
|
24
|
+
# Override the relevant functionality in httpclient to make sspi work.
|
25
|
+
require 'winrm/http/auth'
|
26
|
+
super(endpoint)
|
27
|
+
@httpcli.set_auth(nil, user, pass)
|
28
|
+
# Remove non-sspi auths
|
29
|
+
auths = @httpcli.www_auth.instance_variable_get('@authenticator')
|
30
|
+
auths.delete_if {|i| not i.is_a?(HTTPClient::SSPINegotiateAuth)}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end # WinRM
|
@@ -0,0 +1,256 @@
|
|
1
|
+
|
2
|
+
require 'winrm/helpers/assert_patch'
|
3
|
+
|
4
|
+
def validate_patch
|
5
|
+
# The code below patches the Win32::SSPI module from ruby core to add support
|
6
|
+
# for encrypt/decrypt as described below.
|
7
|
+
# Add few restrictions to make sure the patched methods are still
|
8
|
+
# available, but still give a way to consciously use later versions
|
9
|
+
PatchAssertions.assert_arity_of_patched_method(Win32::SSPI::NegotiateAuth, "initialize", -1)
|
10
|
+
PatchAssertions.assert_arity_of_patched_method(Win32::SSPI::NegotiateAuth, "complete_authentication", 1)
|
11
|
+
PatchAssertions.assert_arity_of_patched_method(Win32::SSPI::NegotiateAuth, "get_credentials", 0)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Perform the patch validations
|
15
|
+
validate_patch
|
16
|
+
|
17
|
+
# Overrides and enhances the ruby core win32 sspi module to add support to
|
18
|
+
# encrypt/decrypt data to be sent over channel, example using SSP Negotiate auth
|
19
|
+
|
20
|
+
module Win32
|
21
|
+
module SSPI
|
22
|
+
# QueryContextAttributes attributes flags
|
23
|
+
SECPKG_ATTR_SIZES = 0x00000000
|
24
|
+
|
25
|
+
module API
|
26
|
+
QueryContextAttributes = Win32API.new("secur32", "QueryContextAttributes", 'pLp', 'L')
|
27
|
+
EncryptMessage = Win32API.new("secur32", "EncryptMessage", 'pLpL', 'L')
|
28
|
+
DecryptMessage = Win32API.new("secur32", "DecryptMessage", 'ppLp', 'L')
|
29
|
+
end
|
30
|
+
|
31
|
+
class SecPkgContext_Sizes
|
32
|
+
attr_accessor :cbMaxToken, :cbMaxSignature, :cbBlockSize, :cbSecurityTrailer
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@cbMaxToken = @cbMaxSignature = @cbBlockSize = @cbSecurityTrailer = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def cbMaxToken
|
39
|
+
@cbMaxToken = @struct.unpack("LLLL")[0] if @struct
|
40
|
+
end
|
41
|
+
|
42
|
+
def cbMaxSignature
|
43
|
+
@cbMaxSignature = @struct.unpack("LLLL")[1] if @struct
|
44
|
+
end
|
45
|
+
|
46
|
+
def cbBlockSize
|
47
|
+
@cbBlockSize = @struct.unpack("LLLL")[2] if @struct
|
48
|
+
end
|
49
|
+
|
50
|
+
def cbSecurityTrailer
|
51
|
+
@cbSecurityTrailer = @struct.unpack("LLLL")[3] if @struct
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_p
|
55
|
+
@struct ||= [@cbMaxToken, @cbMaxSignature, @cbBlockSize, @cbSecurityTrailer].pack("LLLL")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# SecurityBuffer for data to be encrypted
|
60
|
+
class EncryptedSecurityBuffer
|
61
|
+
|
62
|
+
SECBUFFER_DATA = 0x1 # Security buffer data
|
63
|
+
SECBUFFER_TOKEN = 0x2 # Security token
|
64
|
+
SECBUFFER_VERSION = 0
|
65
|
+
|
66
|
+
def initialize(data_buffer, sizes)
|
67
|
+
@original_msg_len = data_buffer.length
|
68
|
+
@cbSecurityTrailer = sizes.cbSecurityTrailer
|
69
|
+
@data_buffer = data_buffer
|
70
|
+
@security_trailer = "\0" * sizes.cbSecurityTrailer
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_p
|
74
|
+
# Assumption is that when to_p is called we are going to get a packed structure. Therefore,
|
75
|
+
# set @unpacked back to nil so we know to unpack when accessors are next accessed.
|
76
|
+
@unpacked = nil
|
77
|
+
# Assignment of inner structure to variable is very important here. Without it,
|
78
|
+
# will not be able to unpack changes to the structure. Alternative, nested unpacks,
|
79
|
+
# does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer")
|
80
|
+
@sec_buffer = [@original_msg_len, SECBUFFER_DATA, @data_buffer, @cbSecurityTrailer, SECBUFFER_TOKEN, @security_trailer].pack("LLPLLP")
|
81
|
+
@struct ||= [SECBUFFER_VERSION, 2, @sec_buffer].pack("LLP")
|
82
|
+
end
|
83
|
+
|
84
|
+
def buffer
|
85
|
+
unpack
|
86
|
+
@buffer
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Unpacks the SecurityBufferDesc structure into member variables. We
|
92
|
+
# only want to do this once per struct, so the struct is deleted
|
93
|
+
# after unpacking.
|
94
|
+
def unpack
|
95
|
+
if ! @unpacked && @sec_buffer && @struct
|
96
|
+
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLPLLP")
|
97
|
+
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLP#{dataBufferSize}LLP#{tokenBufferSize}")
|
98
|
+
# Form the buffer stream as required by server
|
99
|
+
@buffer = [tokenBufferSize].pack("L")
|
100
|
+
@buffer << tokenBuffer << dataBuffer
|
101
|
+
@struct = nil
|
102
|
+
@sec_buffer = nil
|
103
|
+
@unpacked = true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class DecryptedSecurityBuffer
|
109
|
+
|
110
|
+
SECBUFFER_DATA = 0x1 # Security buffer data
|
111
|
+
SECBUFFER_TOKEN = 0x2 # Security token
|
112
|
+
SECBUFFER_VERSION = 0
|
113
|
+
|
114
|
+
def initialize(buffer)
|
115
|
+
# unpack to extract the msg and token
|
116
|
+
token_size, token_buffer, enc_buffer = buffer.unpack("L")
|
117
|
+
@original_msg_len = buffer.length - token_size - 4
|
118
|
+
@cbSecurityTrailer, @security_trailer, @data_buffer = buffer.unpack("La#{token_size}a#{@original_msg_len}")
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_p
|
122
|
+
# Assumption is that when to_p is called we are going to get a packed structure. Therefore,
|
123
|
+
# set @unpacked back to nil so we know to unpack when accessors are next accessed.
|
124
|
+
@unpacked = nil
|
125
|
+
# Assignment of inner structure to variable is very important here. Without it,
|
126
|
+
# will not be able to unpack changes to the structure. Alternative, nested unpacks,
|
127
|
+
# does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer")
|
128
|
+
@sec_buffer = [@original_msg_len, SECBUFFER_DATA, @data_buffer, @cbSecurityTrailer, SECBUFFER_TOKEN, @security_trailer].pack("LLPLLP")
|
129
|
+
@struct ||= [SECBUFFER_VERSION, 2, @sec_buffer].pack("LLP")
|
130
|
+
end
|
131
|
+
|
132
|
+
def buffer
|
133
|
+
unpack
|
134
|
+
@buffer
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
# Unpacks the SecurityBufferDesc structure into member variables. We
|
140
|
+
# only want to do this once per struct, so the struct is deleted
|
141
|
+
# after unpacking.
|
142
|
+
def unpack
|
143
|
+
if ! @unpacked && @sec_buffer && @struct
|
144
|
+
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLPLLP")
|
145
|
+
dataBufferSize, dType, dataBuffer, tokenBufferSize, tType, tokenBuffer = @sec_buffer.unpack("LLP#{dataBufferSize}LLP#{tokenBufferSize}")
|
146
|
+
@buffer = dataBuffer
|
147
|
+
@struct = nil
|
148
|
+
@sec_buffer = nil
|
149
|
+
@unpacked = true
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class NegotiateAuth
|
155
|
+
# Override to remember password
|
156
|
+
# Creates a new instance ready for authentication as the given user in the given domain.
|
157
|
+
# Defaults to current user and domain as defined by ENV["USERDOMAIN"] and ENV["USERNAME"] if
|
158
|
+
# no arguments are supplied.
|
159
|
+
def initialize(user = nil, domain = nil, password = nil)
|
160
|
+
if user.nil? && domain.nil? && ENV["USERNAME"].nil? && ENV["USERDOMAIN"].nil?
|
161
|
+
raise "A username or domain must be supplied since they cannot be retrieved from the environment"
|
162
|
+
end
|
163
|
+
@user = user || ENV["USERNAME"]
|
164
|
+
@domain = domain || ENV["USERDOMAIN"]
|
165
|
+
@password = password
|
166
|
+
end
|
167
|
+
|
168
|
+
# Takes a token and gets the next token in the Negotiate authentication chain. Token can be Base64 encoded or not.
|
169
|
+
# The token can include the "Negotiate" header and it will be stripped.
|
170
|
+
# Does not indicate if SEC_I_CONTINUE or SEC_E_OK was returned.
|
171
|
+
# Token returned is Base64 encoded w/ all new lines removed.
|
172
|
+
def complete_authentication(token)
|
173
|
+
raise "This object is no longer usable because its resources have been freed." if @cleaned_up
|
174
|
+
|
175
|
+
# Nil token OK, just set it to empty string
|
176
|
+
token = "" if token.nil?
|
177
|
+
|
178
|
+
if token.include? "Negotiate"
|
179
|
+
# If the Negotiate prefix is passed in, assume we are seeing "Negotiate <token>" and get the token.
|
180
|
+
token = token.split(" ").last
|
181
|
+
end
|
182
|
+
|
183
|
+
if token.include? B64_TOKEN_PREFIX
|
184
|
+
# indicates base64 encoded token
|
185
|
+
token = token.strip.unpack("m")[0]
|
186
|
+
end
|
187
|
+
|
188
|
+
outputBuffer = SecurityBuffer.new
|
189
|
+
result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, @context.to_p, nil,
|
190
|
+
REQUEST_FLAGS, 0, SECURITY_NETWORK_DREP, SecurityBuffer.new(token).to_p, 0,
|
191
|
+
@context.to_p,
|
192
|
+
outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
|
193
|
+
|
194
|
+
if result.ok? then
|
195
|
+
@auth_successful = true
|
196
|
+
return encode_token(outputBuffer.token)
|
197
|
+
else
|
198
|
+
raise "Error: #{result.to_s}"
|
199
|
+
end
|
200
|
+
ensure
|
201
|
+
# need to make sure we don't clean up if we've already cleaned up.
|
202
|
+
# XXX - clean up later since encrypt/decrypt needs this
|
203
|
+
# clean_up unless @cleaned_up
|
204
|
+
end
|
205
|
+
|
206
|
+
def encrypt_payload(body)
|
207
|
+
if @auth_successful
|
208
|
+
# Approach - http://msdn.microsoft.com/en-us/magazine/cc301890.aspx
|
209
|
+
sizes = SecPkgContext_Sizes.new
|
210
|
+
result = SSPIResult.new(API::QueryContextAttributes.call(@context.to_p, SECPKG_ATTR_SIZES, sizes.to_p))
|
211
|
+
|
212
|
+
outputBuffer = EncryptedSecurityBuffer.new(body, sizes)
|
213
|
+
result = SSPIResult.new(API::EncryptMessage.call(@context.to_p, 0, outputBuffer.to_p, 0 ))
|
214
|
+
encrypted_msg = outputBuffer.buffer
|
215
|
+
|
216
|
+
body = <<-EOF
|
217
|
+
--Encrypted Boundary\r
|
218
|
+
Content-Type: application/HTTP-SPNEGO-session-encrypted\r
|
219
|
+
OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{encrypted_msg.length - sizes.cbSecurityTrailer - 4}\r
|
220
|
+
--Encrypted Boundary\r
|
221
|
+
Content-Type: application/octet-stream\r
|
222
|
+
#{encrypted_msg}--Encrypted Boundary\r
|
223
|
+
EOF
|
224
|
+
end
|
225
|
+
body
|
226
|
+
end
|
227
|
+
|
228
|
+
def decrypt_payload(body)
|
229
|
+
if @auth_successful
|
230
|
+
|
231
|
+
matched_data = /--Encrypted Boundary\s+Content-Type:\s+application\/HTTP-SPNEGO-session-encrypted\s+OriginalContent:\s+type=\S+Length=(\d+)\s+--Encrypted Boundary\s+Content-Type:\s+application\/octet-stream\s+([\S\s]+)--Encrypted Boundary/.match(body)
|
232
|
+
raise "Error: Unencrypted communication not supported. Please check winrm configuration winrm/config/service AllowUnencrypted flag." if matched_data.nil?
|
233
|
+
|
234
|
+
encrypted_msg = matched_data[2]
|
235
|
+
|
236
|
+
outputBuffer = DecryptedSecurityBuffer.new(encrypted_msg)
|
237
|
+
result = SSPIResult.new(API::DecryptMessage.call(@context.to_p, outputBuffer.to_p, 0, 0 ))
|
238
|
+
body = outputBuffer.buffer
|
239
|
+
end
|
240
|
+
body
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
# ** Override to add password support
|
245
|
+
# Gets credentials based on user, domain or both. If both are nil, an error occurs
|
246
|
+
def get_credentials
|
247
|
+
@credentials = CredHandle.new
|
248
|
+
ts = TimeStamp.new
|
249
|
+
@identity = Identity.new @user, @domain, @password
|
250
|
+
result = SSPIResult.new(API::AcquireCredentialsHandle.call(nil, "Negotiate", SECPKG_CRED_OUTBOUND, nil, @identity.to_p,
|
251
|
+
nil, nil, @credentials.to_p, ts.to_p))
|
252
|
+
raise "Error acquire credentials: #{result}" unless result.ok?
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require 'winrm/http/transport_patch'
|
3
|
+
|
4
|
+
module WinRM
|
5
|
+
class WinRMWebService
|
6
|
+
|
7
|
+
# Override winrm to support sspinegotiate option.
|
8
|
+
|
9
|
+
# @param [String,URI] endpoint the WinRM webservice endpoint
|
10
|
+
# @param [Symbol] transport either :kerberos(default)/:ssl/:plaintext
|
11
|
+
# @param [Hash] opts Misc opts for the various transports.
|
12
|
+
# @see WinRM::HTTP::HttpTransport
|
13
|
+
# @see WinRM::HTTP::HttpGSSAPI
|
14
|
+
# @see WinRM::HTTP::HttpSSL
|
15
|
+
def initialize(endpoint, transport = :kerberos, opts = {})
|
16
|
+
@endpoint = endpoint
|
17
|
+
@timeout = DEFAULT_TIMEOUT
|
18
|
+
@max_env_sz = DEFAULT_MAX_ENV_SIZE
|
19
|
+
@locale = DEFAULT_LOCALE
|
20
|
+
case transport
|
21
|
+
when :kerberos
|
22
|
+
require 'gssapi'
|
23
|
+
# TODO: check fo keys and throw error if missing
|
24
|
+
@xfer = HTTP::HttpGSSAPI.new(endpoint, opts[:realm], opts[:service], opts[:keytab], opts)
|
25
|
+
when :plaintext
|
26
|
+
@xfer = HTTP::HttpPlaintext.new(endpoint, opts[:user], opts[:pass], opts)
|
27
|
+
when :sspinegotiate
|
28
|
+
@xfer = HTTP::HttpSSPINegotiate.new(endpoint, opts[:user], opts[:pass], opts)
|
29
|
+
when :ssl
|
30
|
+
@xfer = HTTP::HttpSSL.new(endpoint, opts[:user], opts[:pass], opts[:ca_trust_path], opts)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def assert_prerequisites
|
4
|
+
if windows?
|
5
|
+
require 'winrm-s'
|
6
|
+
["test_winrm_endpoint", "test_winrm_user", "test_winrm_pass"].each do |env_var|
|
7
|
+
raise "\n\nError: Please set the environment variable: #{env_var}\n\n" if ENV[env_var].nil?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
describe "Test sspinegotiate with encrypt/decrypt via WinRM", :windows_and_func_spec_enabled do
|
14
|
+
before(:all) do
|
15
|
+
assert_prerequisites
|
16
|
+
# Remember the user setting.
|
17
|
+
winrm_cfg = %x{winrm get winrm/config/service -format:xml}
|
18
|
+
doc = Nokogiri::XML(winrm_cfg)
|
19
|
+
@original_allowunencrypted_val = doc.at_css("cfg|Service cfg|AllowUnencrypted").text.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
%x{winrm set winrm/config/service @{AllowUnencrypted="false"}}
|
24
|
+
@winrm = WinRM::WinRMWebService.new(ENV["test_winrm_endpoint"], :sspinegotiate, :user => ENV["test_winrm_user"], :pass => ENV["test_winrm_pass"])
|
25
|
+
end
|
26
|
+
|
27
|
+
after(:all) do
|
28
|
+
%x{winrm set winrm/config/service @{AllowUnencrypted="#{@original_allowunencrypted_val}"}}
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should run a CMD command string' do
|
32
|
+
output = @winrm.run_cmd('ipconfig /all')
|
33
|
+
expect(output[:exitcode]).to be_zero
|
34
|
+
expect(output[:data]).not_to be_empty
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should run a CMD command with proper arguments' do
|
38
|
+
output = @winrm.run_cmd('ipconfig', %w{/all})
|
39
|
+
expect(output[:exitcode]).to be_zero
|
40
|
+
expect(output[:data]).not_to be_empty
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should run a CMD command with block' do
|
44
|
+
outvar = ''
|
45
|
+
@winrm.run_cmd('ipconfig', %w{/all}) do |stdout, stderr|
|
46
|
+
outvar << stdout
|
47
|
+
end
|
48
|
+
expect(outvar).to match(/Windows IP Configuration/)
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "Negative test:" do
|
52
|
+
before do
|
53
|
+
%x{winrm set winrm/config/service @{AllowUnencrypted="true"}}
|
54
|
+
end
|
55
|
+
after do
|
56
|
+
%x{winrm set winrm/config/service @{AllowUnencrypted="false"}}
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "when AllowUnencrypted is set to true" do
|
60
|
+
it "should raise an exception" do
|
61
|
+
expect{@winrm.run_cmd('ipconfig')}.to raise_exception
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
def windows?
|
6
|
+
!!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
|
7
|
+
end
|
8
|
+
|
9
|
+
def unix?
|
10
|
+
!windows?
|
11
|
+
end
|
12
|
+
|
13
|
+
def windows_and_func_spec_enabled?
|
14
|
+
windows? and ENV["enable_winrms_func_spec"]
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.filter_run_excluding :windows_only => true unless windows?
|
19
|
+
config.filter_run_excluding :windows_and_func_spec_enabled => true unless windows_and_func_spec_enabled?
|
20
|
+
config.filter_run_excluding :unix_only => true unless unix?
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe "Test if patch is applied on Platform" do
|
5
|
+
|
6
|
+
describe 'Windows is succesfully patched for transport sspinegotiate', :windows_only do
|
7
|
+
before do
|
8
|
+
require 'winrm-s'
|
9
|
+
@winrm = WinRM::WinRMWebService.new("http://mywinrmhost:5985/wsman", :sspinegotiate, :user => "test_winrm_user", :pass => "test_winrm_pass")
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'HTTP::HttpSSPINegotiate class should exist' do
|
13
|
+
expect{WinRM::HTTP::HttpSSPINegotiate}.not_to raise_exception
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should patch httpclient to contain encrypt/decrypt methods' do
|
17
|
+
expect(HTTPClient::SSPINegotiateAuth.new).to respond_to(:encrypt_payload)
|
18
|
+
expect(HTTPClient::SSPINegotiateAuth.new).to respond_to(:decrypt_payload)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'Winrm cannot be patched for unix', :unix_only do
|
24
|
+
it 'require winrm-s should raise exception' do
|
25
|
+
expect{require 'winrm-s'}.to raise_exception
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/winrm-s.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "winrm-s/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "winrm-s"
|
7
|
+
s.version = WinrmS::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.extra_rdoc_files = ["README.md", "LICENSE" ]
|
11
|
+
s.authors = ["Kaustubh Deorukhkar"]
|
12
|
+
s.email = ["dev@getchef.com", "kaustubh@clogeny.com"]
|
13
|
+
s.homepage = "https://github.com/opscode/winrm-s"
|
14
|
+
s.summary = %q{Gem that extends the functionality of the WinRM gem to support the Microsoft Negotiate protocol when authenticating to a remote WinRM endpoint from a Windows system}
|
15
|
+
s.description = s.summary
|
16
|
+
|
17
|
+
s.add_dependency "winrm", "~> 1.1.3"
|
18
|
+
|
19
|
+
%w(rspec-core rspec-expectations rspec-mocks rspec_junit_formatter).each { |gem| s.add_development_dependency gem }
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: winrm-s
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.rc.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kaustubh Deorukhkar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: winrm
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.1.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.1.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-core
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-expectations
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-mocks
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec_junit_formatter
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Gem that extends the functionality of the WinRM gem to support the Microsoft
|
84
|
+
Negotiate protocol when authenticating to a remote WinRM endpoint from a Windows
|
85
|
+
system
|
86
|
+
email:
|
87
|
+
- dev@getchef.com
|
88
|
+
- kaustubh@clogeny.com
|
89
|
+
executables: []
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files:
|
92
|
+
- README.md
|
93
|
+
- LICENSE
|
94
|
+
files:
|
95
|
+
- .gitignore
|
96
|
+
- .rspec
|
97
|
+
- .travis.yml
|
98
|
+
- CHANGELOG.md
|
99
|
+
- CONTRIBUTING.md
|
100
|
+
- Gemfile
|
101
|
+
- LICENSE
|
102
|
+
- README.md
|
103
|
+
- Rakefile
|
104
|
+
- lib/winrm-s.rb
|
105
|
+
- lib/winrm-s/version.rb
|
106
|
+
- lib/winrm/helpers/assert_patch.rb
|
107
|
+
- lib/winrm/http/auth.rb
|
108
|
+
- lib/winrm/http/transport_patch.rb
|
109
|
+
- lib/winrm/win32/sspi.rb
|
110
|
+
- lib/winrm/winrm_service_patch.rb
|
111
|
+
- spec/functional/sspi_nego_encrypt_decrypt_spec.rb
|
112
|
+
- spec/spec_helper.rb
|
113
|
+
- spec/unit/platform_spec.rb
|
114
|
+
- winrm-s.gemspec
|
115
|
+
homepage: https://github.com/opscode/winrm-s
|
116
|
+
licenses: []
|
117
|
+
metadata: {}
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ! '>'
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 1.3.1
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 2.1.11
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: Gem that extends the functionality of the WinRM gem to support the Microsoft
|
138
|
+
Negotiate protocol when authenticating to a remote WinRM endpoint from a Windows
|
139
|
+
system
|
140
|
+
test_files:
|
141
|
+
- spec/functional/sspi_nego_encrypt_decrypt_spec.rb
|
142
|
+
- spec/spec_helper.rb
|
143
|
+
- spec/unit/platform_spec.rb
|
144
|
+
has_rdoc: true
|