├── .expeditor ├── config.yml ├── run_linux_tests.sh ├── update_version.sh └── verify.pipeline.yml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── BUG_TEMPLATE.md │ ├── DESIGN_PROPOSAL.md │ ├── ENHANCEMENT_REQUEST_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── SUPPORT_QUESTION.md ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── VERSION ├── knife-windows.gemspec ├── lib ├── chef │ └── knife │ │ ├── bootstrap_windows_certstore.rb │ │ ├── bootstrap_windows_ssh.rb │ │ ├── bootstrap_windows_winrm.rb │ │ ├── helpers │ │ ├── bootstrap_windows_base.rb │ │ ├── winrm_base.rb │ │ ├── winrm_knife_base.rb │ │ ├── winrm_session.rb │ │ ├── winrm_shared_options.rb │ │ └── wsman_endpoint.rb │ │ ├── windows_cert_generate.rb │ │ ├── windows_cert_install.rb │ │ ├── windows_listener_create.rb │ │ ├── winrm.rb │ │ └── wsman_test.rb └── knife-windows │ └── version.rb ├── sonar-project.properties └── spec ├── assets ├── fake_trusted_certs │ ├── excluded.txt │ ├── github.pem │ └── google.crt └── win_fake_trusted_cert_script.txt ├── data ├── client.d_00 │ ├── 00-foo.rb │ └── foo │ │ └── bar.rb └── client.d_01 │ └── foo │ └── bar.rb ├── dummy_winrm_connection.rb ├── spec_helper.rb └── unit └── knife ├── windows_cert_generate_spec.rb ├── windows_cert_install_spec.rb ├── windows_listener_create_spec.rb ├── winrm_session_spec.rb ├── winrm_spec.rb └── wsman_test_spec.rb /.expeditor/config.yml: -------------------------------------------------------------------------------- 1 | # Documentation available at https://expeditor.chef.io/docs/getting-started/ 2 | --- 3 | 4 | # Slack channel in Chef Software slack to send notifications about build failures, etc 5 | slack: 6 | notify_channel: 7 | - sustaining-notify 8 | - chef-ws-notify 9 | 10 | # This publish is triggered by the `built_in:publish_rubygems` artifact_action. 11 | rubygems: 12 | - knife-windows 13 | 14 | github: 15 | # This deletes the GitHub PR branch after successfully merged into the release branch 16 | delete_branch_on_merge: true 17 | # The tag format to use (e.g. v1.0.0) 18 | version_tag_format: "v{{version}}" 19 | # allow bumping the minor release via label 20 | minor_bump_labels: 21 | - "Expeditor: Bump Version Minor" 22 | # allow bumping the major release via label 23 | major_bump_labels: 24 | - "Expeditor: Bump Version Major" 25 | 26 | changelog: 27 | rollup_header: Changes not yet released to rubygems.org 28 | 29 | subscriptions: 30 | # These actions are taken, in order they are specified, anytime a Pull Request is merged. 31 | - workload: pull_request_merged:{{github_repo}}:{{release_branch}}:* 32 | actions: 33 | - built_in:bump_version: 34 | ignore_labels: 35 | - "Expeditor: Skip Version Bump" 36 | - "Expeditor: Skip All" 37 | - bash:.expeditor/update_version.sh: 38 | only_if: built_in:bump_version 39 | - built_in:update_changelog: 40 | ignore_labels: 41 | - "Expeditor: Skip Changelog" 42 | - "Expeditor: Skip All" 43 | - built_in:build_gem: 44 | only_if: built_in:bump_version 45 | 46 | - workload: project_promoted:{{agent_id}}:* 47 | actions: 48 | - built_in:rollover_changelog 49 | - built_in:publish_rubygems 50 | 51 | pipelines: 52 | - verify: 53 | description: Pull Request validation tests 54 | public: true 55 | -------------------------------------------------------------------------------- /.expeditor/run_linux_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script runs a passed in command, but first setups up the bundler caching on the repo 4 | 5 | set -ue 6 | 7 | export USER="root" 8 | export LANG=C.UTF-8 LANGUAGE=C.UTF-8 9 | 10 | echo "--- bundle install" 11 | 12 | bundle config --local path vendor/bundle 13 | bundle install --jobs=7 --retry=3 14 | 15 | echo "+++ bundle exec task" 16 | bundle exec $@ 17 | -------------------------------------------------------------------------------- /.expeditor/update_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # After a PR merge, Chef Expeditor will bump the PATCH version in the VERSION file. 4 | # It then executes this file to update any other files/components with that new version. 5 | # 6 | 7 | set -evx 8 | 9 | sed -i -r "s/^(\s*)VERSION = \".+\"/\1VERSION = \"$(cat VERSION)\"/" lib/knife-windows/version.rb 10 | 11 | # Once Expeditor finishes executing this script, it will commit the changes and push 12 | # the commit as a new tag corresponding to the value in the VERSION file. 13 | -------------------------------------------------------------------------------- /.expeditor/verify.pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | expeditor: 3 | cached_folders: 4 | - vendor 5 | defaults: 6 | buildkite: 7 | retry: 8 | automatic: 9 | limit: 1 10 | timeout_in_minutes: 30 11 | 12 | steps: 13 | 14 | - label: run-lint-and-specs-ruby-3.1 15 | command: 16 | - .expeditor/run_linux_tests.sh rake 17 | expeditor: 18 | executor: 19 | docker: 20 | image: ruby:3.1-buster 21 | - label: run-lint-and-specs-windows 22 | command: 23 | - gem install chef-utils 24 | - bundle install --jobs=7 --retry=3 25 | - bundle exec rake 26 | expeditor: 27 | executor: 28 | docker: 29 | host_os: windows 30 | image: rubydistros/windows-2019:3.1 31 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Order is important. The last matching pattern has the most precedence. 2 | 3 | * @chef/chef-workstation-owners @chef/chef-workstation-approvers @chef/chef-workstation-reviewers 4 | *.md @chef/docs-team 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: � Bug Report 3 | about: If something isn't working as expected �. 4 | labels: "Status: Untriaged, Type: Bug" 5 | --- 6 | 7 | # Version: 8 | 9 | [Version of the project installed] 10 | 11 | # Environment: 12 | 13 | [Details about the environment such as the Operating System, cookbook details, etc...] 14 | 15 | # Scenario: 16 | 17 | [What you are trying to achieve and you can't?] 18 | 19 | # Steps to Reproduce: 20 | 21 | [If you are filing an issue what are the things we need to do in order to repro your problem?] 22 | 23 | # Expected Result: 24 | 25 | [What are you expecting to happen as the consequence of above reproduction steps?] 26 | 27 | # Actual Result: 28 | 29 | [What actually happens after the reproduction steps?] 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/DESIGN_PROPOSAL.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Design Proposal 3 | about: I have a significant change I would like to propose and discuss before starting 4 | labels: "Status: Untriaged, Type: Design Proposal" 5 | --- 6 | 7 | ### When a Change Needs a Design Proposal 8 | 9 | A design proposal should be opened any time a change meets one of the following qualifications: 10 | 11 | - Significantly changes the user experience of a project in a way that impacts users. 12 | - Significantly changes the underlying architecture of the project in a way that impacts other developers. 13 | - Changes the development or testing process of the project such as a change of CI systems or test frameworks. 14 | 15 | ### Why We Use This Process 16 | 17 | - Allows all interested parties (including any community member) to discuss large impact changes to a project. 18 | - Serves as a durable paper trail for discussions regarding project architecture. 19 | - Forces design discussions to occur before PRs are created. 20 | - Reduces PR refactoring and rejected PRs. 21 | 22 | --- 23 | 24 | 25 | 26 | ## Motivation 27 | 28 | 33 | 34 | ## Specification 35 | 36 | 37 | 38 | ## Downstream Impact 39 | 40 | 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ENHANCEMENT_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Enhancement Request 3 | about: I have a suggestion (and may want to implement it 🙂)! 4 | labels: "Status: Untriaged" 5 | --- 6 | 7 | ### Describe the Enhancement 8 | 9 | 10 | ### Describe the Need 11 | 12 | 13 | ### Current Alternative 14 | 15 | 16 | ### Can We Help You Implement This? 17 | 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | [Please describe what this change achieves] 4 | 5 | ### Issues Resolved 6 | 7 | [List any existing issues this PR resolves, or any Discourse or 8 | StackOverflow discussions that are relevant] 9 | 10 | ### Check List 11 | 12 | - [ ] New functionality includes tests 13 | - [ ] All tests pass 14 | - [ ] All commits have been signed-off for the Developer Certificate of Origin. See 15 | - [ ] PR title is a worthy inclusion in the CHANGELOG -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🤗 Support Question 3 | about: If you have a question 💬, please check out our Slack! 4 | --- 5 | 6 | We use GitHub issues to track bugs and feature requests. If you need help please post to our Mailing List or join the Chef Community Slack. 7 | 8 | * Chef Community Slack at 9 | * Chef Mailing List 10 | 11 | Support issues opened here will be closed and redirected to Slack or Discourse. 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "06:00" 8 | timezone: America/Los_Angeles 9 | open-pull-requests-limit: 10 10 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - main # or the name of your main branch 6 | - develop 7 | - 'release/**' 8 | pull_request: 9 | types: [opened, synchronize, reopened] 10 | jobs: 11 | build: 12 | runs-on: ip-range-controlled 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | - name: SonarQube Scan 18 | uses: sonarsource/sonarqube-scan-action@master 19 | env: 20 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 21 | SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} 22 | # If you wish to fail your job when the Quality Gate is red, uncomment the 23 | # following lines. This would typically be used to fail a deployment. 24 | # We do not recommend to use this in a pull request. Prefer using pull request 25 | # decoration instead. 26 | # - uses: sonarsource/sonarqube-quality-gate-action@main 27 | # timeout-minutes: 5 28 | # env: 29 | # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | ci.gemfile.lock 5 | pkg/* 6 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Layout/DefEndAlignment: 2 | Exclude: 3 | - 'lib/chef/knife/helpers/winrm_knife_base.rb' 4 | 5 | Layout/EndAlignment: 6 | Exclude: 7 | - 'lib/chef/knife/helpers/winrm_knife_base.rb' 8 | 9 | Lint/AmbiguousRegexpLiteral: 10 | Exclude: 11 | - 'lib/chef/knife/helpers/winrm_knife_base.rb' 12 | 13 | Lint/AssignmentInCondition: 14 | Exclude: 15 | - 'lib/chef/knife/helpers/winrm_knife_base.rb' 16 | 17 | Lint/Loop: 18 | Exclude: 19 | - 'lib/chef/knife/windows_cert_generate.rb' 20 | 21 | Lint/UselessAssignment: 22 | Exclude: 23 | - 'lib/chef/knife/helpers/winrm_session.rb' 24 | - 'lib/chef/knife/wsman_test.rb' 25 | 26 | Naming/HeredocDelimiterCase: 27 | Exclude: 28 | - 'lib/chef/knife/helpers/winrm_knife_base.rb' 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # knife-windows Change Log 2 | 3 | Note: this log contains only changes from knife-windows release 0.6.0 and later 4 | -- it does not contain the changes from prior releases. To view change history 5 | prior to release 0.6.0, please visit the [source repository](https://github.com/chef/knife-windows/commits). 6 | 7 | 8 | ## [v5.0.4](https://github.com/chef/knife-windows/tree/v5.0.4) (2025-04-08) 9 | 10 | #### Merged Pull Requests 11 | - use chef-winrm and chef-winrm-elevated [#530](https://github.com/chef/knife-windows/pull/530) ([rishichawda](https://github.com/rishichawda)) 12 | 13 | 14 | 15 | ### Changes not yet released to rubygems.org 16 | 17 | #### Merged Pull Requests 18 | - use chef-winrm and chef-winrm-elevated [#530](https://github.com/chef/knife-windows/pull/530) ([rishichawda](https://github.com/rishichawda)) 19 | - Handle REXML exception [#528](https://github.com/chef/knife-windows/pull/528) ([tpowell-progress](https://github.com/tpowell-progress)) 20 | - CHEF-1917 Fixed the knife-windows verify test failure [#523](https://github.com/chef/knife-windows/pull/523) ([nikhil2611](https://github.com/nikhil2611)) 21 | - Windows Certs PR #3 - this is for knife-windows to be able to consume… [#516](https://github.com/chef/knife-windows/pull/516) ([johnmccrae](https://github.com/johnmccrae)) 22 | - Require Ruby 2.7+ and the knife gem [#517](https://github.com/chef/knife-windows/pull/517) ([tas50](https://github.com/tas50)) 23 | - Fixed knife winrm fails with echo as undefined [#514](https://github.com/chef/knife-windows/pull/514) ([sanga1794](https://github.com/sanga1794)) 24 | - Upgrade to GitHub-native Dependabot [#511](https://github.com/chef/knife-windows/pull/511) ([dependabot-preview[bot]](https://github.com/dependabot-preview[bot])) 25 | - Support external testing [#512](https://github.com/chef/knife-windows/pull/512) ([lamont-granquist](https://github.com/lamont-granquist)) 26 | 27 | 28 | 29 | ## [v4.0.7](https://github.com/chef/knife-windows/tree/v4.0.7) (2021-03-25) 30 | 31 | #### Merged Pull Requests 32 | - Fix test for ruby-3.0 [#510](https://github.com/chef/knife-windows/pull/510) ([lamont-granquist](https://github.com/lamont-granquist)) 33 | 34 | 35 | ## [v4.0.6](https://github.com/chef/knife-windows/tree/v4.0.6) (2020-09-09) 36 | 37 | #### Merged Pull Requests 38 | - autoload winrm [#509](https://github.com/chef/knife-windows/pull/509) ([mwrock](https://github.com/mwrock)) 39 | 40 | ## [v4.0.5](https://github.com/chef/knife-windows/tree/v4.0.5) (2020-08-21) 41 | 42 | #### Merged Pull Requests 43 | - Fix chefstyle violations. [#508](https://github.com/chef/knife-windows/pull/508) ([phiggins](https://github.com/phiggins)) 44 | - Remove old spec files for knife bootstrap [#507](https://github.com/chef/knife-windows/pull/507) ([tas50](https://github.com/tas50)) 45 | - Optimize our requires [#506](https://github.com/chef/knife-windows/pull/506) ([tas50](https://github.com/tas50)) 46 | 47 | ## [v4.0.2](https://github.com/chef/knife-windows/tree/v4.0.2) (2020-05-30) 48 | 49 | #### Merged Pull Requests 50 | - Fix an include of the removed library [#504](https://github.com/chef/knife-windows/pull/504) ([tas50](https://github.com/tas50)) 51 | 52 | ## [v4.0.1](https://github.com/chef/knife-windows/tree/v4.0.1) (2020-05-30) 53 | 54 | #### Merged Pull Requests 55 | - Don't try to require the removed lib [#503](https://github.com/chef/knife-windows/pull/503) ([tas50](https://github.com/tas50)) 56 | 57 | ## [v4.0.0](https://github.com/chef/knife-windows/tree/v4.0.0) (2020-05-29) 58 | 59 | #### Merged Pull Requests 60 | - Chef 16 fixes for knife-windows [#502](https://github.com/chef/knife-windows/pull/502) ([lamont-granquist](https://github.com/lamont-granquist)) 61 | 62 | ## [v3.0.17](https://github.com/chef/knife-windows/tree/v3.0.17) (2020-05-17) 63 | 64 | #### Merged Pull Requests 65 | - Fixed Exception: NameError: uninitialized constant Chef::Knife::Winrm… [#500](https://github.com/chef/knife-windows/pull/500) ([sanga1794](https://github.com/sanga1794)) 66 | 67 | ## [v3.0.16](https://github.com/chef/knife-windows/tree/v3.0.16) (2020-02-11) 68 | 69 | #### Merged Pull Requests 70 | - Further optimize load times for the knife-windows plugins [#497](https://github.com/chef/knife-windows/pull/497) ([tas50](https://github.com/tas50)) 71 | 72 | ## [v3.0.15](https://github.com/chef/knife-windows/tree/v3.0.15) (2020-02-11) 73 | 74 | #### Merged Pull Requests 75 | - Lazy load winrm_session for speedup [#496](https://github.com/chef/knife-windows/pull/496) ([tas50](https://github.com/tas50)) 76 | 77 | ## [v3.0.14](https://github.com/chef/knife-windows/tree/v3.0.14) (2020-02-07) 78 | 79 | #### Merged Pull Requests 80 | - Mark bootstrap deprecated and remove extra windows help [#495](https://github.com/chef/knife-windows/pull/495) ([tas50](https://github.com/tas50)) 81 | 82 | ## [v3.0.13](https://github.com/chef/knife-windows/tree/v3.0.13) (2020-02-07) 83 | 84 | #### Merged Pull Requests 85 | - Fix failures loading the banner [#494](https://github.com/chef/knife-windows/pull/494) ([tas50](https://github.com/tas50)) 86 | 87 | ## [v3.0.12](https://github.com/chef/knife-windows/tree/v3.0.12) (2020-02-07) 88 | 89 | #### Merged Pull Requests 90 | - Require libraries only where we need them [#493](https://github.com/chef/knife-windows/pull/493) ([tas50](https://github.com/tas50)) 91 | 92 | ## [v3.0.11](https://github.com/chef/knife-windows/tree/v3.0.11) (2020-02-04) 93 | 94 | #### Merged Pull Requests 95 | - Fix multiple session issue of concurrency flag [#484](https://github.com/chef/knife-windows/pull/484) ([NAshwini](https://github.com/NAshwini)) 96 | 97 | ## [v3.0.10](https://github.com/chef/knife-windows/tree/v3.0.10) (2020-01-30) 98 | 99 | #### Merged Pull Requests 100 | - Test on the final Ruby 2.7 container + cleanup test files [#489](https://github.com/chef/knife-windows/pull/489) ([tas50](https://github.com/tas50)) 101 | - Run tests on Windows [#490](https://github.com/chef/knife-windows/pull/490) ([tas50](https://github.com/tas50)) 102 | - Apply Chefstyle and enforce style [#491](https://github.com/chef/knife-windows/pull/491) ([tas50](https://github.com/tas50)) 103 | - Remove extra test deps we don't need [#492](https://github.com/chef/knife-windows/pull/492) ([tas50](https://github.com/tas50)) 104 | 105 | ## [v3.0.6](https://github.com/chef/knife-windows/tree/v3.0.6) (2019-12-21) 106 | 107 | #### Merged Pull Requests 108 | - Update README.md as per Chef OSS Best Practices [#483](https://github.com/chef/knife-windows/pull/483) ([vsingh-msys](https://github.com/vsingh-msys)) 109 | - Add testing in Buildkite [#488](https://github.com/chef/knife-windows/pull/488) ([tas50](https://github.com/tas50)) 110 | - Substitute require for require_relative [#487](https://github.com/chef/knife-windows/pull/487) ([tas50](https://github.com/tas50)) 111 | 112 | ## [v3.0.3](https://github.com/chef/knife-windows/tree/v3.0.3) (2019-05-17) 113 | 114 | #### Merged Pull Requests 115 | - Detect if chef-client is already present [#464](https://github.com/chef/knife-windows/pull/464) ([vijaymmali1990](https://github.com/vijaymmali1990)) 116 | - Require Ruby 2.2+ and slim down the files we ship [#465](https://github.com/chef/knife-windows/pull/465) ([tas50](https://github.com/tas50)) 117 | - Using chef/chef path_helper and removed the knife-windows path_helper [#471](https://github.com/chef/knife-windows/pull/471) ([Vasu1105](https://github.com/Vasu1105)) 118 | - Removed deprecated host_key_verification, distro and template_file options [#470](https://github.com/chef/knife-windows/pull/470) ([dheerajd-msys](https://github.com/dheerajd-msys)) 119 | - Require Ruby 2.4 or later [#473](https://github.com/chef/knife-windows/pull/473) ([tas50](https://github.com/tas50)) 120 | - fix log_level incosistency [#476](https://github.com/chef/knife-windows/pull/476) ([dheerajd-msys](https://github.com/dheerajd-msys)) 121 | - Removed support & specs for chefv12 and lower [#475](https://github.com/chef/knife-windows/pull/475) ([dheerajd-msys](https://github.com/dheerajd-msys)) 122 | - Prep branch for knife-windows v3 [#477](https://github.com/chef/knife-windows/pull/477) ([btm](https://github.com/btm)) 123 | - [WIP] Remove knife bootstrap windows [#478](https://github.com/chef/knife-windows/pull/478) ([vsingh-msys](https://github.com/vsingh-msys)) 124 | - Load bootstrap dependency [#480](https://github.com/chef/knife-windows/pull/480) ([vsingh-msys](https://github.com/vsingh-msys)) 125 | - Require Chef Infra 15 [#481](https://github.com/chef/knife-windows/pull/481) ([btm](https://github.com/btm)) 126 | 127 | ## [v1.9.6](https://github.com/chef/knife-windows/tree/v1.9.6) (2018-10-23) 128 | 129 | #### Merged Pull Requests 130 | - [MSYS-850] enable expeditor [#458](https://github.com/chef/knife-windows/pull/458) ([dheerajd-msys](https://github.com/dheerajd-msys)) 131 | - [MSYS-841]fix bootstrap template short name [#457](https://github.com/chef/knife-windows/pull/457) ([dheerajd-msys](https://github.com/dheerajd-msys)) 132 | - MSYS-831 : Fixed windows detection code for windows 2016, windows 2012r2 [#455](https://github.com/chef/knife-windows/pull/455) ([piyushawasthi](https://github.com/piyushawasthi)) 133 | - Adds client_d support to knife-windows [#461](https://github.com/chef/knife-windows/pull/461) ([btm](https://github.com/btm)) 134 | - Slim down the size of the install and the gem [#462](https://github.com/chef/knife-windows/pull/462) ([tas50](https://github.com/tas50)) 135 | 136 | 137 | 138 | ## Release 1.9.1 (2018-03-07) 139 | 140 | * [knife-windows #444](https://github.com/chef/knife-windows/pull/444) Fixes issue when bootstrapping windows systems failing with the message: The input line is too long. 141 | 142 | ## Release 1.9.0 143 | 144 | * [knife-windows #416](https://github.com/chef/knife-windows/pull/416) Add concurrency support via the `--concurrency` flag 145 | 146 | ## Release 1.8.0 147 | 148 | * [knife-windows #407](https://github.com/chef/knife-windows/pull/407) Added value for config_log_level and config_log_location 149 | 150 | ## Release 1.7.1 151 | 152 | * [knife-windows #409](https://github.com/chef/knife-windows/pull/409) Fix trusted_cert copy script generation on windows 153 | 154 | ## Release 1.7.0 155 | 156 | * [knife-windows #400](https://github.com/chef/knife-windows/pull/400) Allow a custom codepage to be specified and passed to the cmd shell 157 | 158 | ## Release 1.6.0 159 | 160 | * [knife-windows #393](https://github.com/chef/knife-windows/pull/393) Add documentation of the --msi-url option 161 | * [knife-windows #392](https://github.com/chef/knife-windows/pull/392) Use winrm v2 and allow users to pass a shell 162 | * [knife-windows #388](https://github.com/chef/knife-windows/pull/388) fix #386 swallowing node_ssl_verify_mode value 163 | * [knife-windows #385](https://github.com/chef/knife-windows/pull/385) Fixed win 2008 64bit ssh bootstrap command hanging 164 | * [knife-windows #384](https://github.com/chef/knife-windows/pull/384) Fix for architechture detection issue for 64 bit 165 | * [knife-windows #381](https://github.com/chef/knife-windows/pull/381) Add validation for FQDN value 166 | * [knife-windows #380](https://github.com/chef/knife-windows/pull/380) Fixing bootstrap via ssh regression 167 | 168 | ## Release 1.5.0 169 | 170 | * [knife-windows #377](https://github.com/chef/knife-windows/pull/377) Added code and corresponding RSpecs to read the json attributes from the --json-attributes-file option. 171 | 172 | ## Release 1.4.1 173 | 174 | * [knife-windows #362](https://github.com/chef/knife-windows/pull/362) Fix `knife windows bootstrap` chef client downloads over a proxy 175 | * [knife-windows #367](https://github.com/chef/knife-windows/pull/367) Honor chef's ssl_policy when making winrm calls 176 | 177 | ## Release 1.4.0 178 | 179 | * [knife-windows #354](https://github.com/chef/knife-windows/pull/354) Allows the user to specify the architecture they want to install on the target system during `knife bootstrap windows`. In your knife config specify `knife[:bootstrap_architecture]`. Valid values are `:i386` for 32 bit or `:x86_64` for 64 bit. By default the architecture will be whatever the target system is. If you try to install a 64 bit package on a 32 bit system you will receive an error. 180 | * [knife-windows #352](https://github.com/chef/knife-windows/pull/352) Have client.rb verify that FIPS mode can be enforced 181 | 182 | ## Release 1.3.0 183 | * [knife-windows #349](https://github.com/chef/knife-windows/pull/349) Pulls in Winrm 1.7.0 which now consumes rubyntlm 0.6.0 to support Extended Protection for Authentication (aka channel binding) for NTLM over TLS 184 | * [knife-windows #350](https://github.com/chef/knife-windows/pull/350) Adding a `--ssl-peer-fingerprint` option as an alternative to `--winrm-ssl-verify-mode verify_none` in self signed scenarios 185 | 186 | ## Release 1.2.1 187 | * [knife-windows #341](https://github.com/chef/knife-windows/pull/341) Removes nokogiri dependency and adds UX fixes for `knife wsman test` when probing a SSL endpoint configured with a self signed certificate 188 | 189 | ## Release 1.2.0 190 | * [knife-windows #334](https://github.com/chef/knife-windows/pull/334) Uses Negotiate authentication via winrm 1.6 on both windows and linux and drops winrm-s dependency 191 | 192 | ## Release 1.1.4 193 | * Bumps winrm-s and winrm dependencies to address a winrm-s incompatibility bug with winrm 1.5 194 | 195 | ## Release 1.1.3 196 | * [knife-windows #329](https://github.com/chef/knife-windows/pull/329) Pin to a minimum winrm-s of 0.3.2 addressing encoding issues in 0.3.1 197 | 198 | ## Release 1.1.2 199 | * [knife-windows #317](https://github.com/chef/knife-windows/pull/317) Update Vault after client is created 200 | * [knife-windows #325](https://github.com/chef/knife-windows/pull/325) Fix proxy configuration to work with chef client 12.6.0 201 | * [knife-windows #326](https://github.com/chef/knife-windows/pull/326) Support new `ssh_identity_file` bootstrap argument 202 | 203 | ## Release 1.1.1 204 | * [knife-windows #307](https://github.com/chef/knife-windows/pull/307) Ensure prompted password is passed to winrm session 205 | * [knife-windows #311](https://github.com/chef/knife-windows/issues/311) WinRM bootstrap silently fails 206 | 207 | ## Release 1.1.0 208 | * [knife-windows #302](https://github.com/chef/knife-windows/pull/302) Address regression caused by chef client 12.5 environment argument 209 | * [knife-windows #295](https://github.com/chef/knife-windows/issues/295) Bootstrap missing policy_group, policy_name feature from Chef Client 12.5 210 | * [knife-windows #296](https://github.com/chef/knife-windows/issues/296) Installing knife-windows produces warning for _all_ knife commands in Mac OS X with ChefDK 0.8.0 211 | * [knife-windows #297](https://github.com/chef/knife-windows/pull/297) use configured proxy settings for all winrm sessions 212 | 213 | ## Release 1.0.0 214 | 215 | * [knife-windows #281](https://github.com/chef/knife-windows/pull/281) Prevent unencrypted negotiate auth, automatically prefix local usernames with '.' for negotiate 216 | * [knife-windows #275](https://github.com/chef/knife-windows/pull/275) Added bootstrap\_install\_command option in parity with knife bootstrap 217 | * [knife-windows #240](https://github.com/chef/knife-windows/pull/240) Change kerberos keytab short option to -T to resolve conflict 218 | * [knife-windows #232](https://github.com/chef/knife-windows/pull/232) Adding --hint option to bootstrap 219 | * [knife-windows #227](https://github.com/chef/knife-windows/issues/227) Exception: NoMethodError: undefined method 'gsub' for false:FalseClass 220 | * [knife-windows #222](https://github.com/chef/knife-windows/issues/222) Validatorless bootstrap support 221 | * [knife-windows #202](https://github.com/chef/knife-windows/issues/202) knife bootstrap windows should support enabling the service 222 | * [knife-windows #213](https://github.com/chef/knife-windows/pull/213) Search possibilities of HOME for bootstrap templates 223 | * [knife-windows #206](https://github.com/chef/knife-windows/pull/206) Add a flag msi_url that allows one to fetch the Chef client msi from a non-chef.io path 224 | * [knife-windows #192](https://github.com/chef/knife-windows/issues/192) deprecate knife bootstrap --distro 225 | * [knife-windows #159](https://github.com/chef/knife-windows/issues/159) `winrm_port` option should default to 5986 if `winrm_transport` option is `ssl` 226 | * [knife-windows #149](https://github.com/chef/knife-windows/pull/149) Adding knife wsman test to validate WSMAN/WinRM availability 227 | * [knife-windows #139](https://github.com/chef/knife-windows/issues/139) Force dev dependency on Chef 11 for test scenarios to avoid Ohai 8 conflict on Ruby 1.9.x 228 | * [knife-windows #126](https://github.com/chef/knife-windows/pull/126) Allow disabling of SSL peer verification in knife-windows for testing 229 | * [knife-windows #154](https://github.com/chef/knife-windows/issues/154) Unreleased regression in master: NameError: undefined local variable or method `path_separator 230 | * [knife-windows #143](https://github.com/chef/knife-windows/issues/143) Unreleased regression in master: WinRM::WinRMHTTPTransportError: Bad HTTP response returned from server (503) in the middle of bootstrap 231 | * [knife-windows #133](https://github.com/chef/knife-windows/issues/133) Bootstrap failure -- unable to validate SSL chef server endpoints 232 | * [knife-windows #132](https://github.com/chef/knife-windows/issues/132) New subcommands for WinRM: windows listener create, cert generate, and cert install 233 | * [knife-windows #129](https://github.com/chef/knife-windows/issues/129) New --winrm-authentication-protocol option for explicit control of authentication 234 | * [knife-windows #125](https://github.com/chef/knife-windows/issues/125) knife-windows should use PowerShell first before cscript to download the Chef Client msi 235 | * [knife-windows #92](https://github.com/chef/knife-windows/issues/92) EventMachine issue: knife bootstrap windows winrm error 236 | * [knife-windows #94](https://github.com/chef/knife-windows/issues/94) Remove Eventmachine dependency 237 | * [knife-windows #252](https://github.com/chef/knife-windows/pull/252) Fail early on ECONNREFUSED, Closes #244. 238 | * [knife-windows #260](https://github.com/chef/knife-windows/pull/260) Fail quickly on invalid option combinations, Closes #259 239 | 240 | ## Release: 0.8.5 241 | * [knife-windows #228](https://github.com/chef/knife-windows/pull/228) make winrm-s dep more strict on knife-windows 0.8.x 242 | 243 | ## Release: 0.8.4 244 | * [knife-windows #133](https://github.com/chef/knife-windows/issues/133) Bootstrap failure -- unable to validate SSL chef server endpoints 245 | 246 | ## Release: 0.8.3 247 | * [knife-windows #131](https://github.com/chef/knife-windows/issues/108) Issue #131: Windows should be bootstrapped using latest Chef Client version compatible with knife's version just like non-Windows systems 248 | * [knife-windows #139](https://github.com/chef/knife-windows/issues/139) Force dev dependency on Chef 11 for test scenarios to avoid Ohai 8 conflict on Ruby 1.9.x 249 | 250 | ## Release: 0.8.2 251 | * [knife-windows #108](https://github.com/chef/knife-windows/issues/108) Error: Unencrypted communication not supported if remote server does not require encryption 252 | 253 | ## Release: 0.8.0 254 | * [knife-windows #98](https://github.com/chef/knife-windows/issues/98) Get winrm command exit code if it is not expected 255 | * [knife-windows #96](https://github.com/chef/knife-windows/issues/96) Fix break from OS patch KB2918614 256 | * Remove the 'instance data' method of creating EC2 servers 257 | * Update winrm-s dependency along with em-winrm and winrm dependencies 258 | * Return failure codes from knife winrm even when `returns` is not set 259 | * Support Windows negotiate authentication protocol when running knife on Windows 260 | 261 | ## Release: 0.6.0 (05/08/2014) 262 | 263 | * [KNIFE-386](https://tickets.opscode.com/browse/KNIFE-386) Wait for a valid command response before bootstrapping over WinRM 264 | * [KNIFE-394](https://tickets.opscode.com/browse/KNIFE-394) Update em-winrm dependency 265 | * [KNIFE-450](https://tickets.opscode.com/browse/KNIFE-450) Set knife winrm command exit status on exception and command failure 266 | 267 | **See source control commit history for earlier changes.** 268 | 269 | ## Selected release notes 270 | These are release notes from very early releases of the plugin. For recent 271 | releases (2014 and later), see the RELEASE_NOTES.md file of each tagged release branch. 272 | 273 | Release Notes - Knife Windows Plugin - Version 0.5.6 274 | 275 | ** New Feature 276 | * new default bootstrap template that installs Chef using official chef-client MSI installer 277 | 278 | Release Notes - Knife Windows Plugin - Version 0.5.4 279 | 280 | ** Bug 281 | * [KNIFE\_WINDOWS-7] - Exception: NoMethodError: undefined method `env_namespace' for Savon:Module 282 | * [KNIFE\_WINDOWS-8] - winrm based bootstrap fails with 'Bad HTTP response returned from server (500)' 283 | 284 | 285 | ** New Feature 286 | * [KNIFE\_WINDOWS-6] - default bootstrap template should support encrypted\_data\_bag\_secret -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Please refer to the Chef Community Code of Conduct at https://www.chef.io/code-of-conduct/ 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please refer to https://github.com/chef/chef/blob/master/CONTRIBUTING.md 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify the gem's dependencies in knife-windows.gemspec 4 | gemspec 5 | 6 | # Necessary for the external tests in https://github.com/chef/chef 7 | if ENV["GEMFILE_MOD"] 8 | puts "GEMFILE_MOD: #{ENV["GEMFILE_MOD"]}" 9 | instance_eval(ENV["GEMFILE_MOD"]) 10 | else 11 | # changed to 18-stable given that it's the newest compatible with 12 | # the latest knife (and knife is embedded in chef so can't just point at 13 | # GitHub) 14 | gem "ohai", git: "https://github.com/chef/ohai", branch: "18-stable" 15 | gem "knife" 16 | end 17 | 18 | group :test do 19 | gem "rspec", "~> 3.0" 20 | gem "rake" 21 | gem "chefstyle" 22 | gem "rb-readline" 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Knife Windows Plugin 2 | ==================== 3 | [![Build status](https://badge.buildkite.com/2e8cc60e947d9ce9e846ec6ef644717cce3ae7a40d6903c3b0.svg?branch=master)](https://buildkite.com/chef-oss/chef-knife-windows-master-verify) 4 | [![Gem Version](https://badge.fury.io/rb/knife-windows.svg)](https://badge.fury.io/rb/knife-windows) 5 | 6 | **Umbrella Project**: [Knife](https://github.com/chef/chef-oss-practices/blob/master/projects/knife.md) 7 | 8 | **Project State**: [Active](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md#active) 9 | 10 | **Issues [Response Time Maximum](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md)**: 14 days 11 | 12 | **Pull Request [Response Time Maximum](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md)**: 14 days 13 | 14 | This plugin adds additional functionality to the Chef Knife CLI tool for 15 | configuring / interacting with nodes running Microsoft Windows: 16 | 17 | * Remote command execution using the WinRM protocol 18 | * Utilities to configure WinRM SSL endpoints on managed nodes 19 | 20 | ## **Knife bootstrap windows ssh/winrm** 21 | 22 | Core Chef now supports bootstrapping Windows systems without a knife plugin 23 | 24 | | knife-windows plugin | Chef Infra Client 15+ | Notes | 25 | |-----:|:-----------|:-----------| 26 | | knife windows bootstrap ssh | knife bootstrap -o ssh or knife bootstrap | Default is 'ssh'| 27 | | knife windows bootstrap winrm | knife bootstrap -o winrm| 28 | 29 | For more detail https://github.com/chef/chef/blob/master/RELEASE_NOTES.md#knife-bootstrap 30 | 31 | ## Subcommands 32 | 33 | This plugin provides the following Knife subcommands. Specific command options can be found by invoking the subcommand with a `--help` flag 34 | 35 | ### knife winrm 36 | 37 | The `winrm` subcommand allows you to invoke commands in parallel on a subset of the nodes in your infrastructure. The `winrm` subcommand uses the same syntax as the [search subcommand](https://docs.chef.io/knife_search.html); you could find the uptime of all your web servers using the command: 38 | 39 | knife winrm "role:web" "net stats srv" -x Administrator -P 'super_secret_password' 40 | 41 | _Please note that to run a single command against multiple nodes, each node must share the same username and password._ 42 | 43 | Or force a chef run: 44 | 45 | knife winrm "myserver.myorganization.net" "chef-client -c c:/chef/client.rb" -m -x Administrator -P "super_secret_password" 46 | myserver.myorganization.net [Fri, 04 Mar 2011 22:00:49 +0000] INFO: Starting Chef Run (Version 0.9.12) 47 | myserver.myorganization.net [Fri, 04 Mar 2011 22:00:50 +0000] WARN: Node ip-0A502FFB has an empty run list. 48 | myserver.myorganization.net [Fri, 04 Mar 2011 22:00:53 +0000] INFO: Chef Run complete in 4.383966 seconds 49 | myserver.myorganization.net [Fri, 04 Mar 2011 22:00:53 +0000] INFO: cleaning the checksum cache 50 | myserver.myorganization.net [Fri, 04 Mar 2011 22:00:53 +0000] INFO: Running report handlers 51 | myserver.myorganization.net [Fri, 04 Mar 2011 22:00:53 +0000] INFO: Report handlers complete 52 | 53 | This subcommand operates in a manner similar to [knife ssh](https://docs.chef.io/knife_ssh.html)...just leveraging the WinRM protocol for communication. It also includes `knife ssh`'s "[interactive session mode](https://docs.chef.io/knife_ssh.html#options)" 54 | 55 | #### winrm-shell 56 | 57 | By default, `knife winrm` runs in a `cmd.exe` shell. You can use the `--winrm-shell` argument to change the shell to `powershell` or `elevated`. An elevated shell is similar to the `powershell` shell but the powershell command is executed from a scheduled task using a local identity. This may be desirable for some operations such as running `chef-client` to converge recipes that work with windows updates, install sql server, etc. 58 | 59 | #### Tip: Use SSL for WinRM communication 60 | 61 | By default, the `knife winrm` subcommands use a plaintext transport, 62 | but they support an option `--winrm-transport` (or `-t`) with the argument 63 | `ssl` that allows the SSL to secure the WinRM payload. Here's an example: 64 | 65 | knife winrm -t ssl "role:web" "net stats srv" -x Administrator -P "super_secret_password" -f ~/server_public_cert.crt 66 | 67 | Use of SSL is strongly recommended, particularly when invoking `knife-windows` on non-Windows platforms, since 68 | without SSL there are limited options for ensuring the privacy of the 69 | plaintext transport. See the section on [Platform authentication 70 | support](#platform-winrm-authentication-support). 71 | 72 | SSL will become the default transport in future revisions of 73 | `knife-windows`. 74 | 75 | ### knife wsman test 76 | 77 | Connects to the remote WSMan/WinRM endpoint and verifies the remote node is listening. This is the equivalent of running Test-Wsman from PowerShell. Endpoints to test can be specified manually, or be driven by search and use many of the same connection options as knife winrm. 78 | To test a single node using the default WinRM port (5985) 79 | 80 | knife wsman test 192.168.1.10 -m 81 | 82 | or to test a single node with SSL enabled on the default port (5986) 83 | 84 | knife wsman test 192.168.1.10 -m --winrm-transport ssl 85 | 86 | or to test all windows nodes registered with your Chef Server organization 87 | 88 | knife wsman test platform:windows 89 | 90 | ### knife windows cert generate 91 | 92 | Generates a certificate(x509) containing a public / private key pair for WinRM 'SSL' communication. 93 | The certificate will be generated in three different formats: 94 | * **.pem** - The *.pem is Base64 encoded public certificate only. One can use this file with the `-f` argument on `knife winrm` command. 95 | * **.pfx** - The PKCS12(i.e .pfx) contains both the public and private keys, usually used on the server. This can be added to a WinRM Server's Certificate Store using `knife windows cert install` (see command description below). **Note:** Do not use the *.pfx file with the `-f` argument on the `knife winrm` command. Use the *.pem file instead. 96 | * **.b64** - The *.b64 is Base64 PKCS12 key pair. Contains both the public and private keys, for upload to the Cloud REST API. e.g. Azure. 97 | 98 | This command also displays the thumbprint of the generated certificate. 99 | 100 | knife windows cert generate --cert-passphrase "strong_passphrase" --hostname "myserver.myorganization.net" --output-file "~/server_cert.pfx" 101 | # This command will generate certificates in the user's home directory with names server_cert.b64, server_cert.pfx and server_cert.pem. 102 | 103 | ### knife windows cert install 104 | 105 | This command only functions on Windows and is intended to be run on a chef node. It adds the specified certificate to its certificate store. This command must include a valid PKCS12(i.e *.pfx) certificate file path such as the *.pfx file generated by `knife windows cert generate` described above. 106 | 107 | knife windows cert install "~/server_cert.pfx" --cert-passphrase "strong_passphrase" 108 | 109 | ### knife windows listener create 110 | This command only functions on Windows and is intended to be run on a chef node. It creates the winrm listener for SSL communication(i.e HTTPS). 111 | This command can also install certificate which is specified using --cert-install option and use the installed certificate thumbprint to create winrm listener. 112 | --hostname option is optional. Default value for hostname is *. 113 | 114 | knife windows listener create --cert-passphrase "strong_passphrase" --hostname "myserver.mydomain.net" --cert-install "~/server_cert.pfx" 115 | 116 | The command also allows you to use existing certificates from local store to create winrm listener. Use --cert-thumbprint option to specify the certificate thumbprint. 117 | 118 | knife windows listener create --cert-passphrase "strong_passphrase" --hostname "myserver.mydomain.net" --cert-thumbprint "bf0fef0bb41be40ceb66a3b38813ca489fe99746" 119 | 120 | You can get the thumbprint for existing certificates in the local store using the following PowerShell command: 121 | 122 | ls cert:\LocalMachine\My 123 | 124 | ## Requirements / setup 125 | 126 | ### Ruby 127 | 128 | Ruby 1.9.3+ is required. 129 | 130 | ### Chef version 131 | 132 | This knife plugins requires >= Chef 11.0.0. More details about Knife plugins can be 133 | [found in the Chef documentation](https://docs.chef.io/plugin_knife.html). 134 | 135 | ## Nodes 136 | 137 | ### WinRM versions 138 | 139 | The node must be running Windows Remote Management (WinRM) 2.0+. WinRM 140 | allows you to call native objects in Windows. This includes, but is not 141 | limited to, running PowerShell scripts, batch scripts, and fetching WMI 142 | data. For more information on WinRM, please visit 143 | [Microsoft's WinRM site](http://msdn.microsoft.com/en-us/library/aa384426\(v=VS.85\).aspx). 144 | 145 | WinRM is built into Windows 7 and Windows Server 2008+. It can also [be installed](https://support.microsoft.com/en-us/kb/968929) on older version of Windows, including: 146 | 147 | * Windows Server 2003 148 | * Windows Vista 149 | 150 | ### WinRM configuration 151 | 152 | **NOTE**: Before any WinRM related knife subcommands will function 153 | a node's WinRM installation must be configured correctly. 154 | The settings below must be added to your base server image or passed 155 | in using some sort of user-data mechanism provided by your cloud 156 | provider. Some cloud providers will set up the required WinRM 157 | configuration through the cloud API for creating instances -- see 158 | the documentation for the provider. 159 | 160 | A server running WinRM must also be configured properly to allow 161 | outside connections for the entire network path from the knife workstation to the server. The easiest way to accomplish this is to use [WinRM's quick configuration option](http://msdn.microsoft.com/en-us/library/aa384372\(v=vs.85\).aspx#quick_default_configuration): 162 | 163 | winrm quickconfig -q 164 | 165 | This will set up an WinRM listener using the HTTP (plaintext) 166 | transport -- WinRM also supports the SSL transport for improved 167 | robustness against information disclosure and other threats. 168 | 169 | The chef-client installation may take more 170 | memory than the default 150MB WinRM allocates per shell on older versions of 171 | Windows (prior to Windows Server 2012) -- this can slow it down 172 | or cause it to fail. The memory limit was increased to 1GB with Windows Management Framework 3 173 | (and Server 2012). However, there is a bug in Windows Management Framework 3 174 | (and Server 2012) which requires a [hotfix from Microsoft](https://support.microsoft.com/en-us/kb/2842230/en-us). 175 | You can increase the memory limit to 1GB with the following PowerShell 176 | command: 177 | 178 | ```powershell 179 | set-item wsman:\localhost\shell\maxmemorypershellmb 1024 180 | ``` 181 | 182 | Commands can take longer than the WinRM default 60 seconds to 183 | complete, optionally increase to 30 minutes if terminates a command prematurely: 184 | 185 | ```powershell 186 | set-item wsman:\localhost\MaxTimeoutms 300000 187 | ``` 188 | 189 | Note that the `winrm` command itself supports the same configuration 190 | capabilities as the PowerShell commands given above -- if you need to 191 | configure WinRM without using PowerShell, use `winrm -?` to get help. 192 | 193 | WinRM supports both the HTTP and HTTPS (SSL) transports and the following 194 | authentication schemes: Kerberos, Digest, Certificate and Basic. The details 195 | of these authentication transports are outside of the scope of this 196 | README but details can be found on the 197 | [WinRM configuration guide](http://msdn.microsoft.com/en-us/library/aa384372\(v=vs.85\).aspx). 198 | 199 | ### Working with legacy Windows versions 200 | 201 | If you are attempting to use `knife winrm` with a version of windows that is older than server 2008 R2 or older than Windows 7 then you may need to alter the default UTF-8 codepage (65001) using the `--winrm-codepage` argument. You can use the codepage native to your locale but `437` is a safe codepage for older Windows versions. 202 | 203 | #### Configure SSL on a Windows node 204 | 205 | WinRM supports use of SSL to provide privacy and integrity of 206 | communication using the protocol and to prevent spoofing attacks. 207 | 208 | ##### Configure SSL using `knife` 209 | 210 | `knife-windows` includes three commands to assist with SSL 211 | configuration -- these commands support all versions of Windows and do 212 | not rely on PowerShell: 213 | 214 | * `knife windows cert generate`: creates a certificate that may be used 215 | to configure an SSL WinRM listener 216 | 217 | * `knife windows cert install`: Installs a certificate into the 218 | Windows certificate store so it can be used to configure an SSL 219 | WinRM listener. 220 | 221 | * `knife windows listener create`: Creates a WinRM listener on a 222 | Windows node -- it can use either a certificate already installed in 223 | the Windows certificate store, or one created by other tools 224 | including the `knife windows cert generate` command. 225 | 226 | Here is an example that configures a listener on the node on which the 227 | commands are executed: 228 | 229 | knife windows cert generate --domain myorg.org --output-file $env:userprofile/winrmcerts/winrm-ssl 230 | knife windows listener create --hostname *.myorg.org --cert-install $env:userprofile/winrmcerts/winrm-ssl.pfx 231 | 232 | Note that the first command which generates the certificate for the 233 | listener could be executed from any system that can run `knife` as 234 | long as the certificate it generates is made available at a path at 235 | which the second command can access it. 236 | 237 | See previous sections for additional details of the `windows cert generate`, `windows cert install` and `windows listener create` subcommands. 238 | 239 | ##### Configure SSL using *Windows Server 2012 or later* 240 | The following PowerShell commands may be used to create an SSL WinRM 241 | listener with a self-signed certificate on Windows 2012R2 or later systems: 242 | 243 | ```powershell 244 | $cert = New-SelfSignedCertificate -DnsName 'myserver.mydomain.org' -CertStoreLocation Cert:\LocalMachine\My 245 | new-item -address * -force -path wsman:\localhost\listener -port 5986 -hostname ($cert.subject -split '=')[1] -transport https -certificatethumbprint $cert.Thumbprint 246 | # Open the firewall for 5986, the default WinRM SSL port 247 | netsh advfirewall firewall set rule name="Windows Remote Management (HTTPS-In)" profile=public protocol=tcp localport=5986 remoteip=localsubnet new remoteip=any 248 | 249 | ``` 250 | 251 | Note that the first command which uses the `New-SelfSignedCertificate` 252 | cmdlet is available only in PowerShell version 4.0 and later. 253 | 254 | ##### Configure SSL using `winrm quickconfig` 255 | 256 | The following command can configure an SSL WinRM listener if the 257 | Windows certificate store's Local Machine store contains a certificate 258 | that meets certain criteria that are most likely to be met if the 259 | system is joined to a Windows Active Directory domain: 260 | 261 | winrm quickconfig -transport:https -q 262 | 263 | If the criteria are not met, an error message will follow with 264 | guidance on the certificate requirements; you may need to obtain a 265 | certificate from the appropriate source or use the PowerShell or 266 | `knife` techniques given above to create the listener instead. 267 | 268 | ##### Disabling peer verification 269 | In the SSL examples above, the `-f` parameter was used to supply a 270 | certificate that could validate the identity of the remote server. 271 | For debugging purposes, this validation may be skipped if you have not 272 | obtained a public certificate that can validate the server. Here is an 273 | example: 274 | 275 | knife winrm -m 192.168.0.6 -x "mydomain\myuser" -P $PASSWD -t ssl --winrm-ssl-verify-mode verify_none ipconfig 276 | 277 | This option should be used carefully since disabling the verification of the 278 | remote system's certificate can subject knife commands to spoofing attacks. 279 | 280 | ##### Connecting securely to self-signed certs 281 | If you generate a self-signed cert, the fqdn and ip may not match which will result in a certificate validation failure. In order to securely connect and reduce the risk of a "Man In The Middle" attack, you may use the certificate's fingerprint to precisely identify the known certificate on the WinRM endpoint. 282 | 283 | The fingerprint can be supplied to ```--ssl-peer-fingerprint``` and instead of using a certificate chain and comparing the CommonName, it will only verify that the fingerprint matches: 284 | 285 | knife winrm --ssl-peer-fingerprint 89255929FB4B5E1BFABF7E7F01AFAFC5E7003C3F \ 286 | -m $IP -x Administrator -P $PASSWD-t ssl --winrm-port 5986 hostname 287 | 10.113.4.54 ip-0A710436 288 | 289 | ## WinRM authentication 290 | 291 | The default authentication protocol for `knife-windows` subcommands that use 292 | WinRM is the Negotiate protocol. The following commands show authentication for domain and local accounts respectively: 293 | 294 | knife winrm -m web1.cloudapp.net -x "proddomain\webuser" -P "super_secret_password" 295 | knife winrm -m db1.cloudapp.net -x "localadmin" -P "super_secret_password" 296 | 297 | The remote system may also be configured with an SSL WinRM listener instead of a 298 | plaintext listener. Then the above commands should be modified to use the SSL 299 | transport as follows using the `-t` (or `--winrm-transport`) option with the 300 | `ssl` argument: 301 | 302 | knife winrm -m web1.cloudapp.net -t ssl -x "proddomain\webuser" -P "super_secret_password" -f ~/mycert.crt 303 | knife winrm -m db1.cloudapp.net -t ssl -x "localadmin" -P "super_secret_password" ~/mycert.crt 304 | 305 | ### Troubleshooting authentication 306 | 307 | Unencrypted traffic with Basic authentication should only be used for low level wire protocol debugging. The configuration for plain text connectivity to 308 | the remote system may be accomplished with the following PowerShell commands: 309 | 310 | ```powershell 311 | set-item wsman:\localhost\service\allowunencrypted $true 312 | set-item wsman:\localhost\service\auth\basic $true 313 | ``` 314 | To use basic authentication connectivity via `knife-windows`, the default 315 | authentication protocol of Negotiate must be overridden using the 316 | `--winrm-authentication-protocol` option with the desired protocol, in this 317 | case Basic: 318 | 319 | knife winrm -m web1.cloudapp.net --winrm-authentication-protocol basic ipconfig -x localadmin -P "super_secret_password" 320 | 321 | Note that when using Basic authentication, domain accounts may not be used for 322 | authentication; an account local to the remote system must be used. 323 | 324 | ### Platform WinRM authentication support 325 | 326 | `knife-windows` supports `Kerberos`, `Negotiate`, and `Basic` authentication 327 | for WinRM communication. 328 | 329 | The following table shows the authentication protocols that can be used with 330 | `knife-windows` depending on whether the knife workstation is a Windows 331 | system, the transport, and whether or not the target user is a domain user or 332 | local to the target Windows system. 333 | 334 | > \* There is a known defect in the `knife winrm` 335 | > winrm` subcommands invoked on any OS platform when authenticating with the Negotiate protocol over 336 | > the SSL transport. The defect is tracked by 337 | > [knife-windows issue #176](https://github.com/chef/knife-windows/issues/176): If the remote system is 338 | > domain-joined, local accounts may not be used to authenticate via Negotiate 339 | > over SSL -- only domain accounts will work. Local accounts will only 340 | > successfully authenticate if the system is not joined to a domain. 341 | > 342 | > This is generally not an issue for bootstrap scenarios, where the 343 | > system has yet to be joined to any domain, but can be a problem for remote 344 | > management cases after the system is domain joined. Workarounds include using 345 | > a domain account instead or bypassing SSL and using Negotiate authentication. 346 | 347 | ## General troubleshooting 348 | 349 | * Windows 2008R2 and earlier versions require an extra configuration 350 | for MaxTimeoutms to avoid WinRM::WinRMHTTPTransportError: Bad HTTP 351 | response error while bootstrapping. It should be at least 300000. 352 | 353 | `set-item wsman:\\localhost\\MaxTimeoutms 300000` 354 | 355 | * When I run the winrm command I get: "Error: Invalid use of command line. Type "winrm -?" for help." 356 | You're running the winrm command from PowerShell and you need to put the key/value pair in single quotes. For example: 357 | 358 | `winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'` 359 | 360 | * If you receive a timeout when trying to connect to your instance for the first time, make sure your Firewall setting 361 | is permissive enough. 362 | 363 | `netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" profile=public protocol=tcp localport=5985 remoteip=localsubnet new remoteip=any` 364 | 365 | ### AWS User Data 366 | 367 | If you are spinning up AWS instances to test against, you can use the following user data when spinning up your instances: 368 | 369 | ``` 370 | 371 | $logfile="C:\\Program Files\\Amazon\\Ec2ConfigService\\Logs\\kitchen-ec2.log" 372 | # Allow script execution 373 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force 374 | # PS Remoting and & winrm.cmd basic config 375 | Enable-PSRemoting -Force -SkipNetworkProfileCheck 376 | & winrm.cmd set winrm/config '@{MaxTimeoutms="1800000"}' >> $logfile 377 | & winrm.cmd set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}' >> $logfile 378 | & winrm.cmd set winrm/config/winrs '@{MaxShellsPerUser="50"}' >> $logfile 379 | #Server settings - support username/password login 380 | & winrm.cmd set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}' >> $logfile 381 | # Firewall Config 382 | & netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" profile=public protocol=tcp localport=5985 remoteip=localsubnet new remoteip=any >> $logfile 383 | 384 | ``` 385 | 386 | ## CONTRIBUTING: 387 | 388 | Please file bugs against the KNIFE_WINDOWS project at https://github.com/chef/knife-windows/issues. 389 | 390 | More information on the contribution process for Chef projects can be found in the [Chef Contributions document](http://docs.chef.io/community_contributions.html). 391 | 392 | # LICENSE: 393 | 394 | Author:: Seth Chisamore () 395 | Copyright:: Copyright (c) 2015-2016 Chef Software, Inc. 396 | License:: Apache License, Version 2.0 397 | 398 | Licensed under the Apache License, Version 2.0 (the "License"); 399 | you may not use this file except in compliance with the License. 400 | You may obtain a copy of the License at 401 | 402 | http://www.apache.org/licenses/LICENSE-2.0 403 | 404 | Unless required by applicable law or agreed to in writing, software 405 | distributed under the License is distributed on an "AS IS" BASIS, 406 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 407 | See the License for the specific language governing permissions and 408 | limitations under the License. 409 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | Bundler::GemHelper.install_tasks 3 | 4 | begin 5 | require "rspec/core/rake_task" 6 | 7 | task default: %i{spec style} 8 | 9 | begin 10 | require "chefstyle" 11 | require "rubocop/rake_task" 12 | desc "Run Chefstyle tests" 13 | RuboCop::RakeTask.new(:style) do |task| 14 | task.options += ["--display-cop-names", "--no-color"] 15 | end 16 | rescue LoadError 17 | puts "chefstyle gem is not installed. bundle install first to make sure all dependencies are installed." 18 | end 19 | 20 | desc "Run all unit specs in spec directory" 21 | RSpec::Core::RakeTask.new(:spec) do |t| 22 | t.pattern = "spec/unit/**/*_spec.rb" 23 | end 24 | 25 | rescue LoadError 26 | STDERR.puts "\n*** RSpec not available. (sudo) gem install rspec to run unit tests. ***\n\n" 27 | end 28 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 5.0.4 -------------------------------------------------------------------------------- /knife-windows.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("lib", __dir__) 2 | require "knife-windows/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "knife-windows" 6 | s.version = Knife::Windows::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ["Seth Chisamore"] 9 | s.email = ["schisamo@chef.io"] 10 | s.license = "Apache-2.0" 11 | s.homepage = "https://github.com/chef/knife-windows" 12 | s.summary = %q{Plugin that adds functionality to Chef Infra's Knife CLI for configuring/interacting with nodes running Microsoft Windows} 13 | s.description = s.summary 14 | 15 | s.required_ruby_version = ">= 3.1" 16 | s.add_dependency "chef", ">= 18.2" 17 | s.add_dependency "chef-winrm", "~> 2.3" 18 | s.add_dependency "chef-winrm-elevated", "~> 1.2" 19 | 20 | s.add_development_dependency "pry" 21 | 22 | s.files = %w{LICENSE} + Dir.glob("{spec,lib}/**/*") 23 | s.test_files = `git ls-files -- spec/*`.split("\n") 24 | s.require_paths = ["lib"] 25 | end 26 | -------------------------------------------------------------------------------- /lib/chef/knife/bootstrap_windows_certstore.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Seth Chisamore () 3 | # Copyright:: Copyright (c) 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 "chef/knife" 20 | require_relative "helpers/bootstrap_windows_base" 21 | 22 | class Chef 23 | class Knife 24 | class BootstrapWindowsCertstore < Bootstrap 25 | include Chef::Knife::BootstrapWindowsBase 26 | 27 | banner "knife bootstrap windows certstore FQDN (options) DEPRECATED" 28 | 29 | option :windows_certstore, 30 | long: "--windows_certstore", 31 | description: "Retrieves the client key from the local Windows Certificate store" 32 | 33 | def run 34 | Chef::Application.fatal!(<<~EOM 35 | *knife windows bootstrap ssh* 36 | Core Chef now supports bootstrapping Windows systems without a knife plugin 37 | 38 | Use 'knife bootstrap -o windows_certstore' instead. 39 | 40 | For more detail https://github.com/chef/chef/blob/master/RELEASE_NOTES.md#knife-bootstrap 41 | EOM 42 | ) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/chef/knife/bootstrap_windows_ssh.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Seth Chisamore () 3 | # Copyright:: Copyright (c) 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 "chef/knife" 20 | require_relative "helpers/bootstrap_windows_base" 21 | 22 | class Chef 23 | class Knife 24 | class BootstrapWindowsSsh < Bootstrap 25 | 26 | include Chef::Knife::BootstrapWindowsBase 27 | 28 | banner "knife bootstrap windows ssh FQDN (options) DEPRECATED" 29 | 30 | option :ssh_user, 31 | short: "-x USERNAME", 32 | long: "--ssh-user USERNAME", 33 | description: "The ssh username", 34 | default: "root" 35 | 36 | option :ssh_password, 37 | short: "-P PASSWORD", 38 | long: "--ssh-password PASSWORD", 39 | description: "The ssh password" 40 | 41 | option :ssh_port, 42 | short: "-p PORT", 43 | long: "--ssh-port PORT", 44 | description: "The ssh port", 45 | proc: Proc.new { |key| key.strip } 46 | 47 | option :ssh_gateway, 48 | short: "-G GATEWAY", 49 | long: "--ssh-gateway GATEWAY", 50 | description: "The ssh gateway" 51 | 52 | option :forward_agent, 53 | short: "-A", 54 | long: "--forward-agent", 55 | description: "Enable SSH agent forwarding", 56 | boolean: true 57 | 58 | option :identity_file, 59 | long: "--identity-file IDENTITY_FILE", 60 | description: "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead." 61 | 62 | option :ssh_identity_file, 63 | short: "-i IDENTITY_FILE", 64 | long: "--ssh-identity-file IDENTITY_FILE", 65 | description: "The SSH identity file used for authentication" 66 | 67 | option :host_key_verify, 68 | long: "--[no-]host-key-verify", 69 | description: "Verify host key, enabled by default.", 70 | boolean: true, 71 | default: true 72 | 73 | def run 74 | Chef::Application.fatal!(<<~EOM 75 | *knife windows bootstrap ssh* 76 | Core Chef now supports bootstrapping Windows systems without a knife plugin 77 | 78 | Use 'knife bootstrap -o ssh' instead. 79 | 80 | For more detail https://github.com/chef/chef/blob/master/RELEASE_NOTES.md#knife-bootstrap 81 | EOM 82 | ) 83 | end 84 | 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/chef/knife/bootstrap_windows_winrm.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Seth Chisamore () 3 | # Copyright:: Copyright (c) 2011-2020 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 "chef/knife" 20 | require_relative "helpers/bootstrap_windows_base" 21 | 22 | class Chef 23 | class Knife 24 | class BootstrapWindowsWinrm < Bootstrap 25 | 26 | include Chef::Knife::BootstrapWindowsBase 27 | 28 | banner "knife bootstrap windows winrm FQDN (options) DEPRECATED" 29 | 30 | def run 31 | Chef::Application.fatal!(<<~EOM 32 | *knife windows bootstrap winrm* 33 | Core Chef now supports bootstrapping Windows systems without a knife plugin 34 | 35 | Use 'knife bootstrap -o winrm' instead. 36 | 37 | For more detail https://github.com/chef/chef/blob/master/RELEASE_NOTES.md#knife-bootstrap 38 | EOM 39 | ) 40 | end 41 | 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/chef/knife/helpers/bootstrap_windows_base.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Seth Chisamore () 3 | # Copyright:: Copyright (c) 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 "chef/knife" 20 | require "chef/util/path_helper" 21 | 22 | class Chef 23 | class Knife 24 | module BootstrapWindowsBase 25 | # :nodoc: 26 | # Would prefer to do this in a rational way, but can't be done b/c of 27 | # Mixlib::CLI's design :( 28 | def self.included(includer) 29 | includer.class_eval do 30 | 31 | option :chef_node_name, 32 | short: "-N NAME", 33 | long: "--node-name NAME", 34 | description: "The Chef node name for your new node" 35 | 36 | option :prerelease, 37 | long: "--prerelease", 38 | description: "Install the pre-release chef gems" 39 | 40 | option :bootstrap_version, 41 | long: "--bootstrap-version VERSION", 42 | description: "The version of Chef to install" 43 | 44 | option :bootstrap_proxy, 45 | long: "--bootstrap-proxy PROXY_URL", 46 | description: "The proxy server for the node being bootstrapped" 47 | 48 | option :bootstrap_no_proxy, 49 | long: "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]", 50 | description: "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode" 51 | 52 | option :bootstrap_install_command, 53 | long: "--bootstrap-install-command COMMANDS", 54 | description: "Custom command to install chef-client" 55 | 56 | option :bootstrap_template, 57 | short: "-t TEMPLATE", 58 | long: "--bootstrap-template TEMPLATE", 59 | description: "Bootstrap Chef using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates." 60 | 61 | option :run_list, 62 | short: "-r RUN_LIST", 63 | long: "--run-list RUN_LIST", 64 | description: "Comma separated list of roles/recipes to apply", 65 | proc: lambda { |o| o.split(",") }, 66 | default: [] 67 | 68 | option :hint, 69 | long: "--hint HINT_NAME[=HINT_FILE]", 70 | description: "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.", 71 | proc: Proc.new { |h, accumulator| 72 | accumulator ||= {} 73 | name, path = h.split("=") 74 | accumulator[name] = path ? Chef::JSONCompat.parse(::File.read(path)) : {} 75 | accumulator 76 | } 77 | 78 | option :first_boot_attributes, 79 | short: "-j JSON_ATTRIBS", 80 | long: "--json-attributes", 81 | description: "A JSON string to be added to the first run of chef-client", 82 | proc: lambda { |o| JSON.parse(o) }, 83 | default: nil 84 | 85 | option :first_boot_attributes_from_file, 86 | long: "--json-attribute-file FILE", 87 | description: "A JSON file to be used to the first run of chef-client", 88 | proc: lambda { |o| Chef::JSONCompat.parse(File.read(o)) }, 89 | default: nil 90 | 91 | # Mismatch between option 'encrypted_data_bag_secret' and it's long value '--secret' is by design for compatibility 92 | option :encrypted_data_bag_secret, 93 | short: "-s SECRET", 94 | long: "--secret ", 95 | description: "The secret key to use to decrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config.", 96 | default: false 97 | 98 | # Mismatch between option 'encrypted_data_bag_secret_file' and it's long value '--secret-file' is by design for compatibility 99 | option :encrypted_data_bag_secret_file, 100 | long: "--secret-file SECRET_FILE", 101 | description: "A file containing the secret key to use to encrypt data bag item values. Will be rendered on the node at c:/chef/encrypted_data_bag_secret and set in the rendered client config." 102 | 103 | option :auth_timeout, 104 | long: "--auth-timeout MINUTES", 105 | description: "The maximum time in minutes to wait to for authentication over the transport to the node to succeed. The default value is 2 minutes.", 106 | default: 2 107 | 108 | option :node_ssl_verify_mode, 109 | long: "--node-ssl-verify-mode [peer|none]", 110 | description: "Whether or not to verify the SSL cert for all HTTPS requests.", 111 | proc: Proc.new { |v| 112 | valid_values = %w{none peer} 113 | unless valid_values.include?(v) 114 | raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}" 115 | end 116 | 117 | v 118 | } 119 | 120 | option :node_verify_api_cert, 121 | long: "--[no-]node-verify-api-cert", 122 | description: "Verify the SSL cert for HTTPS requests to the Chef server API.", 123 | boolean: true 124 | 125 | option :msi_url, 126 | short: "-u URL", 127 | long: "--msi-url URL", 128 | description: "Location of the Chef Client MSI. The default templates will prefer to download from this location. The MSI will be downloaded from chef.io if not provided.", 129 | default: "" 130 | 131 | option :install_as_service, 132 | long: "--install-as-service", 133 | description: "Install chef-client as a Windows service", 134 | default: false 135 | 136 | option :bootstrap_vault_file, 137 | long: "--bootstrap-vault-file VAULT_FILE", 138 | description: "A JSON file with a list of vault(s) and item(s) to be updated" 139 | 140 | option :bootstrap_vault_json, 141 | long: "--bootstrap-vault-json VAULT_JSON", 142 | description: "A JSON string with the vault(s) and item(s) to be updated" 143 | 144 | option :bootstrap_vault_item, 145 | long: "--bootstrap-vault-item VAULT_ITEM", 146 | description: 'A single vault and item to update as "vault:item"', 147 | proc: Proc.new { |i, accumulator| 148 | (vault, item) = i.split(/:/) 149 | accumulator ||= {} 150 | accumulator[vault] ||= [] 151 | accumulator[vault].push(item) 152 | accumulator 153 | } 154 | 155 | option :policy_name, 156 | long: "--policy-name POLICY_NAME", 157 | description: "Policyfile name to use (--policy-group must also be given)", 158 | default: nil 159 | 160 | option :policy_group, 161 | long: "--policy-group POLICY_GROUP", 162 | description: "Policy group name to use (--policy-name must also be given)", 163 | default: nil 164 | 165 | option :tags, 166 | long: "--tags TAGS", 167 | description: "Comma separated list of tags to apply to the node", 168 | proc: lambda { |o| o.split(/[\s,]+/) }, 169 | default: [] 170 | end 171 | end 172 | end 173 | end 174 | end 175 | -------------------------------------------------------------------------------- /lib/chef/knife/helpers/winrm_base.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Seth Chisamore () 3 | # Copyright:: Copyright (c) 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 "chef/knife" 20 | 21 | class Chef 22 | class Knife 23 | module WinrmBase 24 | 25 | # It includes supported WinRM authentication protocol. 26 | WINRM_AUTH_PROTOCOL_LIST ||= %w{basic negotiate kerberos}.freeze 27 | 28 | # :nodoc: 29 | # Would prefer to do this in a rational way, but can't be done b/c of 30 | # Mixlib::CLI's design :( 31 | def self.included(includer) 32 | includer.class_eval do 33 | 34 | deps do 35 | require "chef/encrypted_data_bag_item" 36 | require "kconv" 37 | require "readline" 38 | require "chef/json_compat" 39 | end 40 | 41 | option :winrm_user, 42 | short: "-x USERNAME", 43 | long: "--winrm-user USERNAME", 44 | description: "The WinRM username", 45 | default: "Administrator" 46 | 47 | option :winrm_password, 48 | short: "-P PASSWORD", 49 | long: "--winrm-password PASSWORD", 50 | description: "The WinRM password" 51 | 52 | option :winrm_shell, 53 | long: "--winrm-shell SHELL", 54 | description: "The WinRM shell type. Valid choices are [cmd, powershell, elevated]. 'elevated' runs powershell in a scheduled task", 55 | default: :cmd, 56 | proc: Proc.new { |shell| shell.to_sym } 57 | 58 | option :winrm_transport, 59 | short: "-w TRANSPORT", 60 | long: "--winrm-transport TRANSPORT", 61 | description: "The WinRM transport type. Valid choices are [ssl, plaintext]", 62 | default: "plaintext" 63 | 64 | option :winrm_port, 65 | short: "-p PORT", 66 | long: "--winrm-port PORT", 67 | description: "The WinRM port, by default this is '5985' for 'plaintext' and '5986' for 'ssl' winrm transport" 68 | 69 | option :kerberos_keytab_file, 70 | short: "-T KEYTAB_FILE", 71 | long: "--keytab-file KEYTAB_FILE", 72 | description: "The Kerberos keytab file used for authentication" 73 | 74 | option :kerberos_realm, 75 | short: "-R KERBEROS_REALM", 76 | long: "--kerberos-realm KERBEROS_REALM", 77 | description: "The Kerberos realm used for authentication" 78 | 79 | option :kerberos_service, 80 | short: "-S KERBEROS_SERVICE", 81 | long: "--kerberos-service KERBEROS_SERVICE", 82 | description: "The Kerberos service used for authentication" 83 | 84 | option :ca_trust_file, 85 | short: "-f CA_TRUST_FILE", 86 | long: "--ca-trust-file CA_TRUST_FILE", 87 | description: "The Certificate Authority (CA) trust file used for SSL transport" 88 | 89 | option :winrm_ssl_verify_mode, 90 | long: "--winrm-ssl-verify-mode SSL_VERIFY_MODE", 91 | description: "The WinRM peer verification mode. Valid choices are [verify_peer, verify_none]", 92 | default: :verify_peer, 93 | proc: Proc.new { |verify_mode| verify_mode.to_sym } 94 | 95 | option :ssl_peer_fingerprint, 96 | long: "--ssl-peer-fingerprint FINGERPRINT", 97 | description: "ssl Cert Fingerprint to bypass normal cert chain checks" 98 | 99 | option :winrm_authentication_protocol, 100 | long: "--winrm-authentication-protocol AUTHENTICATION_PROTOCOL", 101 | description: "The authentication protocol used during WinRM communication. The supported protocols are #{WINRM_AUTH_PROTOCOL_LIST.join(",")}. Default is 'negotiate'.", 102 | default: "negotiate" 103 | 104 | option :session_timeout, 105 | long: "--session-timeout Minutes", 106 | description: "The timeout for the client for the maximum length of the WinRM session", 107 | default: 30 108 | 109 | option :winrm_codepage, 110 | long: "--winrm-codepage Codepage", 111 | description: "The codepage to use for the winrm cmd shell", 112 | default: 65001 113 | end 114 | end 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /lib/chef/knife/helpers/winrm_knife_base.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Steven Murawski ( 1 ? "nodes" : "node"} found, " + 84 | "but does not have the required attribute (#{config[:attribute]}) to establish the connection. " + 85 | "Try setting another attribute to open the connection using --attribute.") 86 | end 87 | exit 10 88 | end 89 | end 90 | 91 | # TODO: Copied from Knife::Core:GenericPresenter. Should be extracted 92 | def extract_nested_value(data, nested_value_spec) 93 | nested_value_spec.split(".").each do |attr| 94 | if data.nil? 95 | nil # don't get no method error on nil 96 | elsif data.respond_to?(attr.to_sym) 97 | data = data.send(attr.to_sym) 98 | elsif data.respond_to?(:[]) 99 | data = data[attr] 100 | else 101 | data = begin 102 | data.send(attr.to_sym) 103 | rescue NoMethodError 104 | nil 105 | end 106 | end 107 | end 108 | ( !data.is_a?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data 109 | end 110 | 111 | def run_command(command = "") 112 | relay_winrm_command(command) 113 | check_for_errors! 114 | @exit_code 115 | end 116 | 117 | def relay_winrm_command(command) 118 | Chef::Log.debug(command) 119 | @session_results = [] 120 | queue = Queue.new 121 | @winrm_sessions.each { |s| queue << s } 122 | num_sessions = config[:concurrency] 123 | num_targets = @winrm_sessions.length 124 | num_sessions = (num_sessions.nil? || num_sessions == 0) ? num_targets : [num_sessions, num_targets].min 125 | 126 | # These nils will kill the Threads once no more sessions are left 127 | num_sessions.times { queue << nil } 128 | threads = [] 129 | num_sessions.times do 130 | threads << Thread.new do 131 | while session = queue.pop 132 | run_command_in_thread(session, command) 133 | end 134 | end 135 | end 136 | threads.map(&:join) 137 | @session_results 138 | end 139 | 140 | private 141 | 142 | def run_command_in_thread(s, command) 143 | @session_results << s.relay_command(command) 144 | rescue WinRM::WinRMHTTPTransportError, WinRM::WinRMAuthorizationError => e 145 | if authorization_error?(e) 146 | unless config[:suppress_auth_failure] 147 | # Display errors if the caller hasn't opted to retry 148 | ui.error "Failed to authenticate to #{s.host} as #{config[:winrm_user]}" 149 | ui.info "Response: #{e.message}" 150 | ui.info get_failed_authentication_hint 151 | raise e 152 | end 153 | else 154 | raise e 155 | end 156 | end 157 | 158 | def get_failed_authentication_hint 159 | if @session_opts[:basic_auth_only] 160 | FAILED_BASIC_HINT 161 | else 162 | FAILED_NOT_BASIC_HINT 163 | end 164 | end 165 | 166 | def authorization_error?(exception) 167 | exception.is_a?(WinRM::WinRMAuthorizationError) || 168 | exception.message =~ /401/ 169 | end 170 | 171 | def check_for_errors! 172 | @exit_code ||= 0 173 | @winrm_sessions.each do |session| 174 | session_exit_code = session.exit_code 175 | unless success_return_codes.include? session_exit_code.to_i 176 | @exit_code = [@exit_code, session_exit_code.to_i].max 177 | ui.error "Failed to execute command on #{session.host} return code #{session_exit_code}" 178 | end 179 | end 180 | end 181 | 182 | def success_return_codes 183 | # Redundant if the CLI options parsing occurs 184 | return [0] unless config[:returns] 185 | 186 | @success_return_codes ||= config[:returns].split(",").collect(&:to_i) 187 | end 188 | 189 | def session_from_list 190 | @list.each do |item| 191 | Chef::Log.debug("Adding #{item}") 192 | @session_opts[:host] = item 193 | create_winrm_session(@session_opts) 194 | end 195 | end 196 | 197 | def create_winrm_session(options = {}) 198 | session = Chef::Knife::WinrmSession.new(options) 199 | @winrm_sessions ||= [] 200 | @winrm_sessions.push(session) 201 | end 202 | 203 | def resolve_session_options 204 | config[:winrm_port] ||= ( config[:winrm_transport] == "ssl" ) ? "5986" : "5985" 205 | 206 | @session_opts = { 207 | user: resolve_winrm_user, 208 | password: config[:winrm_password], 209 | port: config[:winrm_port], 210 | operation_timeout: resolve_winrm_session_timeout, 211 | basic_auth_only: resolve_winrm_basic_auth, 212 | disable_sspi: resolve_winrm_disable_sspi, 213 | transport: resolve_winrm_transport, 214 | no_ssl_peer_verification: resolve_no_ssl_peer_verification, 215 | ssl_peer_fingerprint: resolve_ssl_peer_fingerprint, 216 | shell: config[:winrm_shell], 217 | codepage: config[:winrm_codepage], 218 | } 219 | 220 | if @session_opts[:user] && (not @session_opts[:password]) 221 | @session_opts[:password] = config[:winrm_password] = get_password 222 | end 223 | 224 | if @session_opts[:transport] == :kerberos 225 | @session_opts.merge!(resolve_winrm_kerberos_options) 226 | end 227 | 228 | @session_opts[:ca_trust_path] = config[:ca_trust_file] if config[:ca_trust_file] 229 | end 230 | 231 | def resolve_winrm_user 232 | user = config[:winrm_user] 233 | 234 | # Prefixing with '.\' when using negotiate 235 | # to auth user against local machine domain 236 | if resolve_winrm_basic_auth || 237 | resolve_winrm_transport == :kerberos || 238 | user.include?("\\") || 239 | user.include?("@") 240 | user 241 | else 242 | ".\\#{user}" 243 | end 244 | end 245 | 246 | def resolve_winrm_session_timeout 247 | # 30 min (Default) OperationTimeout for long bootstraps fix for KNIFE_WINDOWS-8 248 | config[:session_timeout].to_i * 60 if config[:session_timeout] 249 | end 250 | 251 | def resolve_winrm_basic_auth 252 | config[:winrm_authentication_protocol] == "basic" 253 | end 254 | 255 | def resolve_winrm_kerberos_options 256 | kerberos_opts = {} 257 | kerberos_opts[:keytab] = config[:kerberos_keytab_file] if config[:kerberos_keytab_file] 258 | kerberos_opts[:realm] = config[:kerberos_realm] if config[:kerberos_realm] 259 | kerberos_opts[:service] = config[:kerberos_service] if config[:kerberos_service] 260 | kerberos_opts 261 | end 262 | 263 | def resolve_winrm_transport 264 | transport = config[:winrm_transport].to_sym 265 | if config.any? { |k, v| k.to_s =~ /kerberos/ && !v.nil? } 266 | transport = :kerberos 267 | elsif transport != :ssl && negotiate_auth? 268 | transport = :negotiate 269 | end 270 | 271 | transport 272 | end 273 | 274 | def resolve_no_ssl_peer_verification 275 | config[:ca_trust_file].nil? && config[:winrm_ssl_verify_mode] == :verify_none && resolve_winrm_transport == :ssl 276 | end 277 | 278 | def resolve_ssl_peer_fingerprint 279 | config[:ssl_peer_fingerprint] 280 | end 281 | 282 | def resolve_winrm_disable_sspi 283 | resolve_winrm_transport != :negotiate 284 | end 285 | 286 | def get_password 287 | @password ||= ui.ask("Enter your password: ", echo: false) 288 | end 289 | 290 | def negotiate_auth? 291 | config[:winrm_authentication_protocol] == "negotiate" 292 | end 293 | 294 | def warn_no_ssl_peer_verification 295 | unless @@ssl_warning_given 296 | @@ssl_warning_given = true 297 | ui.warn(<<~WARN) 298 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 299 | SSL validation of HTTPS requests for the WinRM transport is disabled. HTTPS WinRM 300 | connections are still encrypted, but knife is not able to detect forged replies 301 | or spoofing attacks. 302 | 303 | To fix this issue add an entry like this to your knife configuration file: 304 | 305 | ``` 306 | # Verify all WinRM HTTPS connections (default, recommended) 307 | knife[:winrm_ssl_verify_mode] = :verify_peer 308 | ``` 309 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 310 | WARN 311 | end 312 | end 313 | 314 | end 315 | end 316 | end 317 | end 318 | end 319 | -------------------------------------------------------------------------------- /lib/chef/knife/helpers/winrm_session.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Steven Murawski 3 | # Copyright:: Copyright (c) 2015-2016 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 "chef/knife" 20 | require "chef/application" 21 | autoload :WinRM, "chef-winrm-elevated" 22 | 23 | class Chef 24 | class Knife 25 | class WinrmSession 26 | attr_reader :host, :endpoint, :port, :output, :error, :exit_code 27 | 28 | def initialize(options) 29 | configure_proxy 30 | 31 | @host = options[:host] 32 | @port = options[:port] 33 | @user = options[:user] 34 | @shell_args = [ options[:shell] ] 35 | @shell_args << { codepage: options[:codepage] } if options[:shell] == :cmd 36 | url = "#{options[:host]}:#{options[:port]}/wsman" 37 | scheme = options[:transport] == :ssl ? "https" : "http" 38 | @endpoint = "#{scheme}://#{url}" 39 | 40 | opts = {} 41 | opts = { 42 | user: @user, 43 | password: options[:password], 44 | basic_auth_only: options[:basic_auth_only], 45 | disable_sspi: options[:disable_sspi], 46 | no_ssl_peer_verification: options[:no_ssl_peer_verification], 47 | ssl_peer_fingerprint: options[:ssl_peer_fingerprint], 48 | endpoint: endpoint, 49 | transport: options[:transport], 50 | } 51 | options[:transport] == :kerberos ? opts.merge!({ service: options[:service], realm: options[:realm] }) : opts.merge!({ ca_trust_path: options[:ca_trust_path] }) 52 | opts[:operation_timeout] = options[:operation_timeout] if options[:operation_timeout] 53 | Chef::Log.debug("WinRM::WinRMWebService options: #{opts}") 54 | Chef::Log.debug("Endpoint: #{endpoint}") 55 | Chef::Log.debug("Transport: #{options[:transport]}") 56 | 57 | @winrm_session = WinRM::Connection.new(opts) 58 | @winrm_session.logger = Chef::Log 59 | 60 | transport = @winrm_session.send(:transport) 61 | http_client = transport.instance_variable_get(:@httpcli) 62 | Chef::HTTP::DefaultSSLPolicy.new(http_client.ssl_config).set_custom_certs 63 | end 64 | 65 | def relay_command(command) 66 | session_result = WinRM::Output.new 67 | @winrm_session.shell(*@shell_args) do |shell| 68 | shell.username = @user.split("\\").last if shell.respond_to?(:username) 69 | session_result = shell.run(command) do |stdout, stderr| 70 | print_data(@host, stdout) if stdout 71 | print_data(@host, stderr, :red) if stderr 72 | end 73 | end 74 | @exit_code = session_result.exitcode 75 | session_result 76 | rescue WinRM::WinRMHTTPTransportError, WinRM::WinRMAuthorizationError => e 77 | @exit_code = 401 78 | raise e 79 | end 80 | 81 | private 82 | 83 | def print_data(host, data, color = :cyan) 84 | if data =~ /\n/ 85 | data.split(/\n/).each { |d| print_data(host, d, color) } 86 | elsif !data.nil? 87 | print Chef::Knife::Winrm.ui.color(host, color) 88 | puts " #{data}" 89 | end 90 | end 91 | 92 | def configure_proxy 93 | if Chef::Config.respond_to?(:export_proxies) 94 | Chef::Config.export_proxies 95 | else 96 | Chef::Application.new.configure_proxy_environment_variables 97 | end 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/chef/knife/helpers/winrm_shared_options.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Steven Murawski () 2 | # Copyright:: Copyright (c) 2014-2016 Chef Software, Inc. 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require "chef/knife" 19 | require_relative "helpers/winrm_base" 20 | require "chef/mixin/powershell_exec" 21 | 22 | class Chef 23 | class Knife 24 | class WindowsCertGenerate < Knife 25 | 26 | include Chef::Mixin::PowershellExec 27 | 28 | attr_accessor :thumbprint, :hostname 29 | 30 | banner "knife windows cert generate -H HOST_NAME (options)" 31 | 32 | deps do 33 | require "openssl" unless defined?(OpenSSL) 34 | require "socket" unless defined?(Socket) 35 | end 36 | 37 | option :hostname, 38 | short: "-H HOSTNAME", 39 | long: "--hostname HOSTNAME", 40 | description: "Use to specify the hostname for the listener. 41 | For example, --hostname something.mydomain.com or *.mydomain.com.", 42 | required: true 43 | 44 | option :output_file, 45 | short: "-o PATH", 46 | long: "--output-file PATH", 47 | description: "Specifies the file path at which to generate the 3 certificate files of type .pfx, .b64, and .pem. If you omit this option we use c:\\chef\\cache\\chef- as the filename for each certificate type" 48 | 49 | option :key_length, 50 | short: "-k LENGTH", 51 | long: "--key-length LENGTH", 52 | description: "Default is 2048", 53 | default: "2048" 54 | 55 | option :cert_validity, 56 | long: "--cert-validity MONTHS", 57 | description: "Default is 24 months", 58 | default: "24" 59 | 60 | option :cert_passphrase, 61 | long: "--cert-passphrase PASSWORD", 62 | description: "Password for certificate." 63 | 64 | option :store_in_certstore, 65 | long: "--store_in_certstore true", 66 | description: "Tells knife to store the password for your certificates in the Windows Registry for later retrieval." 67 | 68 | def generate_keypair 69 | OpenSSL::PKey::RSA.new(config[:key_length].to_i) 70 | end 71 | 72 | def prompt_for_passphrase 73 | passphrase = "" 74 | begin 75 | print "Passphrases do not match. Try again.\n" unless passphrase.empty? 76 | print "Enter certificate passphrase (empty for no passphrase):" 77 | passphrase = STDIN.gets 78 | return passphrase.strip if passphrase == "\n" 79 | 80 | print "Enter same passphrase again:" 81 | confirm_passphrase = STDIN.gets 82 | end until passphrase == confirm_passphrase 83 | passphrase.strip 84 | end 85 | 86 | def generate_certificate(rsa_key) 87 | @hostname = config[:hostname] if config[:hostname] 88 | 89 | # Create a self-signed X509 certificate from the rsa_key (unencrypted) 90 | cert = OpenSSL::X509::Certificate.new 91 | cert.version = 2 92 | cert.serial = Random.rand(65534) + 1 # 2 digit byte range random number for better security aspect 93 | 94 | cert.subject = OpenSSL::X509::Name.parse "/CN=#{@hostname}" 95 | cert.issuer = cert.subject 96 | cert.public_key = rsa_key.public_key 97 | cert.not_before = Time.now 98 | cert.not_after = cert.not_before + 2 * 365 * config[:cert_validity].to_i * 60 * 60 # 2 years validity 99 | ef = OpenSSL::X509::ExtensionFactory.new 100 | ef.subject_certificate = cert 101 | ef.issuer_certificate = cert 102 | cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false)) 103 | cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false)) 104 | cert.add_extension(ef.create_extension("extendedKeyUsage", "1.3.6.1.5.5.7.3.1", false)) 105 | cert.sign(rsa_key, OpenSSL::Digest.new("SHA1")) 106 | @thumbprint = OpenSSL::Digest::SHA1.new(cert.to_der) 107 | cert 108 | end 109 | 110 | def write_certificate_to_file(cert, file_path, rsa_key, store_key) 111 | File.open(file_path + ".pem", "wb") { |f| f.print cert.to_pem } 112 | 113 | config[:cert_passphrase] = prompt_for_passphrase unless config[:cert_passphrase] 114 | 115 | if store_key == true 116 | set_local_password(config[:cert_passphrase]) 117 | end 118 | 119 | pfx = OpenSSL::PKCS12.create("#{config[:cert_passphrase]}", file_path, rsa_key, cert) 120 | File.open(file_path + ".pfx", "wb") { |f| f.print pfx.to_der } 121 | File.open(file_path + ".b64", "wb") { |f| f.print Base64.strict_encode64(pfx.to_der) } 122 | end 123 | 124 | # in the world of No Certs On Disk, we store a password for a p12/pfx in Keychain or the Registry. A p12/Pfx MUST have a password associated with it because it holds a private key 125 | # Here we check to see if that password is already set. 126 | def check_for_local_password 127 | if ChefUtils.windows? 128 | powershell_code = <<-CHECKFORPASSWORD 129 | Try { 130 | $localpass = Get-ItemPropertyValue -Path "HKLM:\\Software\Progress\Authenticator" -Name "PfxPass" -ErrorAction Stop 131 | return $localpass 132 | } 133 | Catch { 134 | return $false 135 | } 136 | CHECKFORPASSWORD 137 | powershell_exec!(powershell_code).result 138 | elsif ChefUtils.macos? 139 | nil 140 | end 141 | end 142 | 143 | def set_local_password(password) 144 | print "The password you just specified is being stored in the Registry. It will be used as the default until explicitly updated\n" 145 | more_powershell_code = <<-SETTHEPASSWORD 146 | $my_pwd = ConvertTo-SecureString -String "#{password}" -Force -AsPlainText; 147 | if (-not (Test-Path HKLM:\\SOFTWARE\\Progress)){ 148 | New-Item -Path "HKLM:\\SOFTWARE\\Progress\\Authenticator" -Force 149 | New-ItemProperty -path "HKLM:\\SOFTWARE\\Progress\\Authenticator" -name "PfxPass" -value $my_pwd -PropertyType String 150 | } 151 | else{ 152 | Set-ItemProperty -path "HKLM:\\SOFTWARE\\Progress\\Authenticator" -name "PfxPass" -value $my_pwd 153 | } 154 | 155 | SETTHEPASSWORD 156 | powershell_exec!(more_powershell_code) 157 | end 158 | 159 | def certificates_already_exist?(file_path) 160 | certs_exists = false 161 | %w{pem pfx b64}.each do |extn| 162 | if File.exist?("#{file_path}.#{extn}") 163 | certs_exists = true 164 | break 165 | end 166 | end 167 | 168 | if certs_exists 169 | begin 170 | confirm("Do you really want to overwrite existing certificates") 171 | rescue SystemExit # Need to handle this as confirming with N/n raises SystemExit exception 172 | exit! 173 | end 174 | end 175 | end 176 | 177 | def run 178 | STDOUT.sync = STDERR.sync = true 179 | 180 | # takes user specified first cli value as a destination file path for generated cert. 181 | # allowing for output_file to be ommitted 182 | if config[:output_file] == nil? 183 | config[:output_file] = File.join(::Chef::Config[:file_cache_path], "chef-#{config[:hostname]}") 184 | end 185 | 186 | file_path = @name_args.empty? ? config[:output_file].sub(/\.(\w+)$/, "") : @name_args.first 187 | 188 | # check if certs already exists at given file path 189 | certificates_already_exist? file_path 190 | 191 | if config[:store_in_certstore] == "true" || config[:store_in_certstore] == "True" || config[:store_in_certstore] == "TRUE" 192 | store_key = true 193 | else 194 | store_key = false 195 | end 196 | 197 | begin 198 | filename = File.basename(file_path) 199 | rsa_key = generate_keypair 200 | cert = generate_certificate rsa_key 201 | write_certificate_to_file cert, file_path, rsa_key, store_key 202 | ui.info "Generated Certificates:" 203 | ui.info "- #{filename}.pfx - PKCS12 format key pair. Contains public and private keys, can be used with an SSL server." 204 | ui.info "- #{filename}.b64 - Base64 encoded PKCS12 key pair. Contains public and private keys, used by some cloud provider API's to configure SSL servers." 205 | ui.info "- #{filename}.pem - Base64 encoded public certificate only. Required by the client to connect to the server." 206 | ui.info "Certificate Thumbprint: #{@thumbprint.to_s.upcase}" 207 | rescue => e 208 | puts "ERROR: + #{e}" 209 | end 210 | end 211 | 212 | end 213 | end 214 | end 215 | -------------------------------------------------------------------------------- /lib/chef/knife/windows_cert_install.rb: -------------------------------------------------------------------------------- 1 | # Author:: Mukta Aphale () 2 | # Copyright:: Copyright (c) 2014-2016 Chef Software, Inc. 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require "chef/knife" 19 | require_relative "helpers/winrm_base" 20 | 21 | class Chef 22 | class Knife 23 | class WindowsCertInstall < Knife 24 | 25 | deps do 26 | require "chef/mixin/powershell_exec" 27 | extend Chef::Mixin::PowershellExec 28 | end 29 | 30 | banner "knife windows cert install CERT [CERT] (options)" 31 | 32 | option :cert_passphrase, 33 | short: "-cp PASSWORD", 34 | long: "--cert-passphrase PASSWORD", 35 | description: "Password for certificate." 36 | 37 | def get_cert_passphrase 38 | print "Enter given certificate's passphrase (empty for no passphrase):" 39 | passphrase = STDIN.gets 40 | passphrase.strip 41 | end 42 | 43 | def run 44 | STDOUT.sync = STDERR.sync = true 45 | 46 | if Chef::Platform.windows? 47 | if @name_args.empty? 48 | ui.error "Please specify the certificate path. e.g- 'knife windows cert install " 49 | exit 1 50 | end 51 | file_path = @name_args.first 52 | config[:cert_passphrase] = get_cert_passphrase unless config[:cert_passphrase] 53 | 54 | begin 55 | ui.info "Adding certificate to the Windows Certificate Store..." 56 | result = `powershell.exe -Command " '#{config[:cert_passphrase]}' | certutil -importPFX '#{file_path}' AT_KEYEXCHANGE"` 57 | if $?.exitstatus == 0 58 | ui.info "Certificate added to Certificate Store" 59 | else 60 | ui.info "Error adding the certificate. Use -VV option for details" 61 | end 62 | Chef::Log.debug "#{result}" 63 | rescue => e 64 | puts "ERROR: + #{e}" 65 | end 66 | else 67 | ui.error "Certificate can be installed on Windows system only" 68 | exit 1 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/chef/knife/windows_listener_create.rb: -------------------------------------------------------------------------------- 1 | # Author:: Mukta Aphale () 2 | # Copyright:: Copyright (c) Chef Software Inc. 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require "chef/knife" 19 | require_relative "helpers/winrm_base" 20 | 21 | class Chef 22 | class Knife 23 | class WindowsListenerCreate < Knife 24 | deps do 25 | require "openssl" unless defined?(OpenSSL) 26 | end 27 | 28 | banner "knife windows listener create (options)" 29 | 30 | option :cert_install, 31 | short: "-c CERT_PATH", 32 | long: "--cert-install CERT_PATH", 33 | description: "Adds specified certificate to the Windows Certificate Store's Local Machine personal store before creating listener." 34 | 35 | option :port, 36 | short: "-p PORT", 37 | long: "--port PORT", 38 | description: "Specify port. Default is 5986", 39 | default: "5986" 40 | 41 | option :hostname, 42 | short: "-h HOSTNAME", 43 | long: "--hostname HOSTNAME", 44 | description: "Hostname on the listener. Default is blank", 45 | default: "" 46 | 47 | option :cert_thumbprint, 48 | short: "-t THUMBPRINT", 49 | long: "--cert-thumbprint THUMBPRINT", 50 | description: "Thumbprint of the certificate. Required only if --cert-install option is not used." 51 | 52 | option :cert_passphrase, 53 | short: "-cp PASSWORD", 54 | long: "--cert-passphrase PASSWORD", 55 | description: "Password for certificate." 56 | 57 | def get_cert_passphrase 58 | print "Enter given certificate's passphrase (empty for no passphrase):" 59 | passphrase = STDIN.gets 60 | passphrase.strip 61 | end 62 | 63 | def exitstatus 64 | $?.exitstatus 65 | end 66 | 67 | def run 68 | STDOUT.sync = STDERR.sync = true 69 | 70 | if Chef::Platform.windows? 71 | begin 72 | if config[:cert_install] 73 | config[:cert_passphrase] = get_cert_passphrase unless config[:cert_passphrase] 74 | result = `powershell.exe -Command " '#{config[:cert_passphrase]}' | certutil -importPFX '#{config[:cert_install]}' AT_KEYEXCHANGE"` 75 | if exitstatus 76 | ui.info "Certificate installed to Certificate Store" 77 | result = `powershell.exe -Command " echo (Get-PfxCertificate #{config[:cert_install]}).thumbprint "` 78 | ui.info "Certificate Thumbprint: #{result}" 79 | config[:cert_thumbprint] = result.strip 80 | else 81 | ui.error "Error installing certificate to Certificate Store" 82 | ui.error result 83 | exit 1 84 | end 85 | end 86 | 87 | unless config[:cert_thumbprint] 88 | ui.error "Please specify the --cert-thumbprint" 89 | exit 1 90 | end 91 | 92 | result = `winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname="#{config[:hostname]}";CertificateThumbprint="#{config[:cert_thumbprint]}";Port="#{config[:port]}"}` 93 | Chef::Log.debug result 94 | 95 | if exitstatus == 0 96 | ui.info "WinRM listener created with Port: #{config[:port]} and CertificateThumbprint: #{config[:cert_thumbprint]}" 97 | else 98 | ui.error "Error creating WinRM listener. use -VV for more details." 99 | exit 1 100 | end 101 | 102 | rescue => e 103 | puts "ERROR: + #{e}" 104 | end 105 | else 106 | ui.error "WinRM listener can be created on Windows system only" 107 | exit 1 108 | end 109 | end 110 | 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/chef/knife/winrm.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Seth Chisamore () 3 | # Copyright:: Copyright (c) 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 "chef/knife" 20 | require_relative "helpers/winrm_knife_base" # WinrmCommandSharedFunctions 21 | 22 | class Chef 23 | class Knife 24 | class Winrm < Knife 25 | 26 | include Chef::Knife::WinrmCommandSharedFunctions 27 | 28 | deps do 29 | require_relative "windows_cert_generate" 30 | require_relative "windows_cert_install" 31 | require_relative "windows_listener_create" 32 | require_relative "helpers/winrm_session" 33 | require "readline" 34 | require "chef/search/query" 35 | end 36 | 37 | attr_writer :password 38 | 39 | banner "knife winrm QUERY COMMAND (options)" 40 | 41 | option :returns, 42 | long: "--returns CODES", 43 | description: "A comma delimited list of return codes which indicate success", 44 | default: "0" 45 | 46 | def run 47 | STDOUT.sync = STDERR.sync = true 48 | 49 | configure_session 50 | exit_status = execute_remote_command 51 | if exit_status != 0 52 | exit exit_status 53 | else 54 | exit_status 55 | end 56 | end 57 | 58 | def execute_remote_command 59 | case @name_args[1] 60 | when "interactive" 61 | interactive 62 | else 63 | run_command(@name_args[1..-1].join(" ")) 64 | end 65 | end 66 | 67 | private 68 | 69 | def interactive 70 | puts "WARN: Deprecated functionality. This will not be supported in future knife-windows releases." 71 | puts "Connected to #{ui.list(session.servers.collect { |s| ui.color(s.host, :cyan) }, :inline, " and ")}" 72 | puts 73 | puts "To run a command on a list of servers, do:" 74 | puts " on SERVER1 SERVER2 SERVER3; COMMAND" 75 | puts " Example: on latte foamy; echo foobar" 76 | puts 77 | puts "To exit interactive mode, use 'quit!'" 78 | puts 79 | loop do 80 | command = read_line 81 | case command 82 | when "quit!" 83 | puts "Bye!" 84 | break 85 | when /^on (.+?); (.+)$/ 86 | raw_list = $1.split(" ") 87 | server_list = [] 88 | @winrm_sessions.each do |session_server| 89 | server_list << session_server if raw_list.include?(session_server.host) 90 | end 91 | command = $2 92 | relay_winrm_command(command, server_list) 93 | else 94 | relay_winrm_command(command) 95 | end 96 | end 97 | end 98 | 99 | # Present the prompt and read a single line from the console. It also 100 | # detects ^D and returns "exit" in that case. Adds the input to the 101 | # history, unless the input is empty. Loops repeatedly until a non-empty 102 | # line is input. 103 | def read_line 104 | loop do 105 | command = reader.readline("#{ui.color("knife-winrm>", :bold)} ", true) 106 | 107 | if command.nil? 108 | command = "exit" 109 | puts(command) 110 | else 111 | command.strip! 112 | end 113 | 114 | unless command.empty? 115 | return command 116 | end 117 | end 118 | end 119 | 120 | def reader 121 | Readline 122 | end 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /lib/chef/knife/wsman_test.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Steven Murawski () 3 | # Copyright:: Copyright (c) 2015-2016 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 "chef/knife" 20 | require_relative "helpers/winrm_knife_base" 21 | require_relative "helpers/wsman_endpoint" 22 | 23 | class Chef 24 | class Knife 25 | class WsmanTest < Knife 26 | 27 | include Chef::Knife::WinrmCommandSharedFunctions 28 | 29 | deps do 30 | require "httpclient" 31 | require "chef/search/query" 32 | end 33 | 34 | banner "knife wsman test QUERY (options)" 35 | 36 | def run 37 | # pass a dummy password to avoid prompt for password 38 | # but it does nothing 39 | @config[:winrm_password] = "cute_little_kittens" 40 | 41 | configure_session 42 | verify_wsman_accessiblity_for_nodes 43 | end 44 | 45 | private 46 | 47 | def verify_wsman_accessiblity_for_nodes 48 | error_count = 0 49 | @winrm_sessions.each do |item| 50 | Chef::Log.debug("checking for WSMAN availability at #{item.endpoint}") 51 | 52 | ssl_error = nil 53 | begin 54 | response = post_identity_request(item.endpoint) 55 | ui.msg "Connected successfully to #{item.host} at #{item.endpoint}." 56 | rescue Exception => err 57 | end 58 | 59 | output_object = parse_response(item, response) 60 | output_object.error_message += "\r\nError returned from endpoint: #{err.message}" if err 61 | 62 | unless output_object.error_message.nil? 63 | ui.warn "Failed to connect to #{item.host} at #{item.endpoint}." 64 | if err && err.is_a?(OpenSSL::SSL::SSLError) 65 | ui.warn "Failure due to an issue with SSL; likely cause would be unsuccessful certificate verification." 66 | ui.warn "Either ensure your certificate is valid or use '--winrm-ssl-verify-mode verify_none' to ignore verification failures." 67 | end 68 | error_count += 1 69 | end 70 | 71 | if config[:verbosity] >= 1 72 | output(output_object) 73 | end 74 | end 75 | if error_count > 0 76 | ui.error "Failed to connect to #{error_count} nodes." 77 | exit 1 78 | end 79 | end 80 | 81 | def post_identity_request(endpoint) 82 | xml = '' 83 | header = { 84 | "WSMANIDENTIFY" => "unauthenticated", 85 | "Content-Type" => "application/soap+xml; charset=UTF-8", 86 | } 87 | 88 | client = HTTPClient.new 89 | Chef::HTTP::DefaultSSLPolicy.new(client.ssl_config).set_custom_certs 90 | client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE if resolve_no_ssl_peer_verification 91 | client.post(endpoint, xml, header) 92 | end 93 | 94 | def parse_response(node, response) 95 | output_object = Chef::Knife::WsmanEndpoint.new(node.host, node.port, node.endpoint) 96 | output_object.response_status_code = response.status_code unless response.nil? 97 | 98 | if response.nil? || response.status_code != 200 99 | output_object.error_message = "No valid WSMan endoint listening at #{node.endpoint}." 100 | else 101 | begin 102 | doc = REXML::Document.new(response.body) 103 | output_object.protocol_version = search_xpath(doc, "//wsmid:ProtocolVersion") 104 | output_object.product_version = search_xpath(doc, "//wsmid:ProductVersion") 105 | output_object.product_vendor = search_xpath(doc, "//wsmid:ProductVendor") 106 | if output_object.protocol_version.to_s.strip.length == 0 107 | output_object.error_message = "Endpoint #{node.endpoint} on #{node.host} does not appear to be a WSMAN endpoint. Response body was #{response.body}" 108 | end 109 | rescue REXML::ParseException => e 110 | output_object.error_message = e.message 111 | end 112 | end 113 | output_object 114 | end 115 | 116 | def search_xpath(document, property_name) 117 | result = REXML::XPath.match(document, property_name) 118 | result[0].nil? ? "" : result[0].text 119 | end 120 | end 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/knife-windows/version.rb: -------------------------------------------------------------------------------- 1 | module Knife 2 | module Windows 3 | VERSION = "5.0.4".freeze 4 | MAJOR, MINOR, TINY = VERSION.split(".") 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=chef_knife-windows_AYciC8YaJ4YHsO5MtJQc -------------------------------------------------------------------------------- /spec/assets/fake_trusted_certs/excluded.txt: -------------------------------------------------------------------------------- 1 | This file should be excluded in the output of 2 | Chef::Knife::Core::WindowsBootstrapContext#trusted_certs_script 3 | -------------------------------------------------------------------------------- /spec/assets/fake_trusted_certs/github.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIHeTCCBmGgAwIBAgIQC/20CQrXteZAwwsWyVKaJzANBgkqhkiG9w0BAQsFADB1 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 4 | d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk 5 | IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE2MDMxMDAwMDAwMFoXDTE4MDUxNzEy 6 | MDAwMFowgf0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB 7 | BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF 8 | Ewc1MTU3NTUwMSQwIgYDVQQJExs4OCBDb2xpbiBQIEtlbGx5LCBKciBTdHJlZXQx 9 | DjAMBgNVBBETBTk0MTA3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p 10 | YTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMu 11 | MRMwEQYDVQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 12 | CgKCAQEA54hc8pZclxgcupjiA/F/OZGRwm/ZlucoQGTNTKmBEgNsrn/mxhngWmPw 13 | bAvUaLP//T79Jc+1WXMpxMiz9PK6yZRRFuIo0d2bx423NA6hOL2RTtbnfs+y0PFS 14 | /YTpQSelTuq+Fuwts5v6aAweNyMcYD0HBybkkdosFoDccBNzJ92Ac8I5EVDUc3Or 15 | /4jSyZwzxu9kdmBlBzeHMvsqdH8SX9mNahXtXxRpwZnBiUjw36PgN+s9GLWGrafd 16 | 02T0ux9Yzd5ezkMxukqEAQ7AKIIijvaWPAJbK/52XLhIy2vpGNylyni/DQD18bBP 17 | T+ZG1uv0QQP9LuY/joO+FKDOTler4wIDAQABo4IDejCCA3YwHwYDVR0jBBgwFoAU 18 | PdNQpdagre7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFIhcSGcZzKB2WS0RecO+oqyH 19 | IidbMCUGA1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1Ud 20 | DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0f 21 | BG4wbDA0oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy 22 | dmVyLWcxLmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTIt 23 | ZXYtc2VydmVyLWcxLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsG 24 | AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGI 25 | BggrBgEFBQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0 26 | LmNvbTBSBggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp 27 | Z2lDZXJ0U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMB 28 | Af8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgCkuQmQtBhYFIe7E6LM 29 | Z3AKPDWYBPkb37jjd80OyA3cEAAAAVNhieoeAAAEAwBHMEUCIQCHHSEY/ROK2/sO 30 | ljbKaNEcKWz6BxHJNPOtjSyuVnSn4QIgJ6RqvYbSX1vKLeX7vpnOfCAfS2Y8lB5R 31 | NMwk6us2QiAAdgBo9pj4H2SCvjqM7rkoHUz8cVFdZ5PURNEKZ6y7T0/7xAAAAVNh 32 | iennAAAEAwBHMEUCIQDZpd5S+3to8k7lcDeWBhiJASiYTk2rNAT26lVaM3xhWwIg 33 | NUqrkIODZpRg+khhp8ag65B8mu0p4JUAmkRDbiYnRvYAdwBWFAaaL9fC7NP14b1E 34 | sj7HRna5vJkRXMDvlJhV1onQ3QAAAVNhieqZAAAEAwBIMEYCIQDnm3WStlvE99GC 35 | izSx+UGtGmQk2WTokoPgo1hfiv8zIAIhAPrYeXrBgseA9jUWWoB4IvmcZtshjXso 36 | nT8MIG1u1zF8MA0GCSqGSIb3DQEBCwUAA4IBAQCLbNtkxuspqycq8h1EpbmAX0wM 37 | 5DoW7hM/FVdz4LJ3Kmftyk1yd8j/PSxRrAQN2Mr/frKeK8NE1cMji32mJbBqpWtK 38 | /+wC+avPplBUbNpzP53cuTMF/QssxItPGNP5/OT9Aj1BxA/NofWZKh4ufV7cz3pY 39 | RDS4BF+EEFQ4l5GY+yp4WJA/xSvYsTHWeWxRD1/nl62/Rd9FN2NkacRVozCxRVle 40 | FrBHTFxqIP6kDnxiLElBrZngtY07ietaYZVLQN/ETyqLQftsf8TecwTklbjvm8NT 41 | JqbaIVifYwqwNN+4lRxS3F5lNlA/il12IOgbRioLI62o8G0DaEUQgHNf8vSG 42 | -----END CERTIFICATE----- 43 | -------------------------------------------------------------------------------- /spec/assets/fake_trusted_certs/google.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIHLzCCBhegAwIBAgIIe8v/Wprl7jIwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE 3 | BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl 4 | cm5ldCBBdXRob3JpdHkgRzIwHhcNMTYxMTAzMDExMjAwWhcNMTcwMTI2MDExMjAw 5 | WjBmMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN 6 | TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEVMBMGA1UEAwwMKi5n 7 | b29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7eUw+Kb3fhbNikiv 8 | UBVwP1Rqn3EUL2AQULxRgcXUL1qYuP3GHcZNmSdtDvj7Dmr0MPBcKzZ69HyWeH08 9 | tCKTGqOCBMcwggTDMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCCA4YG 10 | A1UdEQSCA30wggN5ggwqLmdvb2dsZS5jb22CDSouYW5kcm9pZC5jb22CFiouYXBw 11 | ZW5naW5lLmdvb2dsZS5jb22CEiouY2xvdWQuZ29vZ2xlLmNvbYIWKi5nb29nbGUt 12 | YW5hbHl0aWNzLmNvbYILKi5nb29nbGUuY2GCCyouZ29vZ2xlLmNsgg4qLmdvb2ds 13 | ZS5jby5pboIOKi5nb29nbGUuY28uanCCDiouZ29vZ2xlLmNvLnVrgg8qLmdvb2ds 14 | ZS5jb20uYXKCDyouZ29vZ2xlLmNvbS5hdYIPKi5nb29nbGUuY29tLmJygg8qLmdv 15 | b2dsZS5jb20uY2+CDyouZ29vZ2xlLmNvbS5teIIPKi5nb29nbGUuY29tLnRygg8q 16 | Lmdvb2dsZS5jb20udm6CCyouZ29vZ2xlLmRlggsqLmdvb2dsZS5lc4ILKi5nb29n 17 | bGUuZnKCCyouZ29vZ2xlLmh1ggsqLmdvb2dsZS5pdIILKi5nb29nbGUubmyCCyou 18 | Z29vZ2xlLnBsggsqLmdvb2dsZS5wdIISKi5nb29nbGVhZGFwaXMuY29tgg8qLmdv 19 | b2dsZWFwaXMuY26CFCouZ29vZ2xlY29tbWVyY2UuY29tghEqLmdvb2dsZXZpZGVv 20 | LmNvbYIMKi5nc3RhdGljLmNugg0qLmdzdGF0aWMuY29tggoqLmd2dDEuY29tggoq 21 | Lmd2dDIuY29tghQqLm1ldHJpYy5nc3RhdGljLmNvbYIMKi51cmNoaW4uY29tghAq 22 | LnVybC5nb29nbGUuY29tghYqLnlvdXR1YmUtbm9jb29raWUuY29tgg0qLnlvdXR1 23 | YmUuY29tghYqLnlvdXR1YmVlZHVjYXRpb24uY29tggsqLnl0aW1nLmNvbYIaYW5k 24 | cm9pZC5jbGllbnRzLmdvb2dsZS5jb22CC2FuZHJvaWQuY29tghtkZXZlbG9wZXIu 25 | YW5kcm9pZC5nb29nbGUuY26CBGcuY2+CBmdvby5nbIIUZ29vZ2xlLWFuYWx5dGlj 26 | cy5jb22CCmdvb2dsZS5jb22CEmdvb2dsZWNvbW1lcmNlLmNvbYIZcG9saWN5Lm10 27 | YS1zdHMuZ29vZ2xlLmNvbYIKdXJjaGluLmNvbYIKd3d3Lmdvby5nbIIIeW91dHUu 28 | YmWCC3lvdXR1YmUuY29tghR5b3V0dWJlZWR1Y2F0aW9uLmNvbTALBgNVHQ8EBAMC 29 | B4AwaAYIKwYBBQUHAQEEXDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2ds 30 | ZS5jb20vR0lBRzIuY3J0MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29v 31 | Z2xlLmNvbS9vY3NwMB0GA1UdDgQWBBR5MCCHaX4fFVGbf9djLVzXRIxRIjAMBgNV 32 | HRMBAf8EAjAAMB8GA1UdIwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMCEGA1Ud 33 | IAQaMBgwDAYKKwYBBAHWeQIFATAIBgZngQwBAgIwMAYDVR0fBCkwJzAloCOgIYYf 34 | aHR0cDovL3BraS5nb29nbGUuY29tL0dJQUcyLmNybDANBgkqhkiG9w0BAQsFAAOC 35 | AQEAKS3dMw7fxdPK6n4q61wnVvTcomfJltDzPuGJj5VrRu/CrUq6DLC3Wgdc3US1 36 | wlnlD73QI+6gplL4Zm77LfBILhOmJXnOuW+ktx96bOOhOO5r4V6KX5InpSRBLdWr 37 | 3hyj9DpfmZEJsNeuiJktfBUPKfZ+wsKrRkiY8QEgDT/0y0SRF5yIW5JOnyJY9pJ6 38 | iJmd29rMeHSmN4JzNOFrrx+RbvyhSDUmOIl1skLDhtdsW8ttuKkyz+Iu2wxXxSr6 39 | yerkMyqTYXSXs16o1uRo8lEbAnXtV6YIAtK3qbxMZbQUzGkNEiWT7FTasyKC+UlZ 40 | URDYCDWBHebcIogs7r6ouYq5qw== 41 | -----END CERTIFICATE----- 42 | -------------------------------------------------------------------------------- /spec/assets/win_fake_trusted_cert_script.txt: -------------------------------------------------------------------------------- 1 | > C:\chef/trusted_certs/google.pem ( 2 | echo.-----BEGIN CERTIFICATE----- 3 | echo.MIIHLzCCBhegAwIBAgIIe8v/Wprl7jIwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE 4 | echo.BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl 5 | echo.cm5ldCBBdXRob3JpdHkgRzIwHhcNMTYxMTAzMDExMjAwWhcNMTcwMTI2MDExMjAw 6 | echo.WjBmMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN 7 | echo.TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEVMBMGA1UEAwwMKi5n 8 | echo.b29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7eUw+Kb3fhbNikiv 9 | echo.UBVwP1Rqn3EUL2AQULxRgcXUL1qYuP3GHcZNmSdtDvj7Dmr0MPBcKzZ69HyWeH08 10 | echo.tCKTGqOCBMcwggTDMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCCA4YG 11 | echo.A1UdEQSCA30wggN5ggwqLmdvb2dsZS5jb22CDSouYW5kcm9pZC5jb22CFiouYXBw 12 | echo.ZW5naW5lLmdvb2dsZS5jb22CEiouY2xvdWQuZ29vZ2xlLmNvbYIWKi5nb29nbGUt 13 | echo.YW5hbHl0aWNzLmNvbYILKi5nb29nbGUuY2GCCyouZ29vZ2xlLmNsgg4qLmdvb2ds 14 | echo.ZS5jby5pboIOKi5nb29nbGUuY28uanCCDiouZ29vZ2xlLmNvLnVrgg8qLmdvb2ds 15 | echo.ZS5jb20uYXKCDyouZ29vZ2xlLmNvbS5hdYIPKi5nb29nbGUuY29tLmJygg8qLmdv 16 | echo.b2dsZS5jb20uY2+CDyouZ29vZ2xlLmNvbS5teIIPKi5nb29nbGUuY29tLnRygg8q 17 | echo.Lmdvb2dsZS5jb20udm6CCyouZ29vZ2xlLmRlggsqLmdvb2dsZS5lc4ILKi5nb29n 18 | echo.bGUuZnKCCyouZ29vZ2xlLmh1ggsqLmdvb2dsZS5pdIILKi5nb29nbGUubmyCCyou 19 | echo.Z29vZ2xlLnBsggsqLmdvb2dsZS5wdIISKi5nb29nbGVhZGFwaXMuY29tgg8qLmdv 20 | echo.b2dsZWFwaXMuY26CFCouZ29vZ2xlY29tbWVyY2UuY29tghEqLmdvb2dsZXZpZGVv 21 | echo.LmNvbYIMKi5nc3RhdGljLmNugg0qLmdzdGF0aWMuY29tggoqLmd2dDEuY29tggoq 22 | echo.Lmd2dDIuY29tghQqLm1ldHJpYy5nc3RhdGljLmNvbYIMKi51cmNoaW4uY29tghAq 23 | echo.LnVybC5nb29nbGUuY29tghYqLnlvdXR1YmUtbm9jb29raWUuY29tgg0qLnlvdXR1 24 | echo.YmUuY29tghYqLnlvdXR1YmVlZHVjYXRpb24uY29tggsqLnl0aW1nLmNvbYIaYW5k 25 | echo.cm9pZC5jbGllbnRzLmdvb2dsZS5jb22CC2FuZHJvaWQuY29tghtkZXZlbG9wZXIu 26 | echo.YW5kcm9pZC5nb29nbGUuY26CBGcuY2+CBmdvby5nbIIUZ29vZ2xlLWFuYWx5dGlj 27 | echo.cy5jb22CCmdvb2dsZS5jb22CEmdvb2dsZWNvbW1lcmNlLmNvbYIZcG9saWN5Lm10 28 | echo.YS1zdHMuZ29vZ2xlLmNvbYIKdXJjaGluLmNvbYIKd3d3Lmdvby5nbIIIeW91dHUu 29 | echo.YmWCC3lvdXR1YmUuY29tghR5b3V0dWJlZWR1Y2F0aW9uLmNvbTALBgNVHQ8EBAMC 30 | echo.B4AwaAYIKwYBBQUHAQEEXDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2ds 31 | echo.ZS5jb20vR0lBRzIuY3J0MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29v 32 | echo.Z2xlLmNvbS9vY3NwMB0GA1UdDgQWBBR5MCCHaX4fFVGbf9djLVzXRIxRIjAMBgNV 33 | echo.HRMBAf8EAjAAMB8GA1UdIwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMCEGA1Ud 34 | echo.IAQaMBgwDAYKKwYBBAHWeQIFATAIBgZngQwBAgIwMAYDVR0fBCkwJzAloCOgIYYf 35 | echo.aHR0cDovL3BraS5nb29nbGUuY29tL0dJQUcyLmNybDANBgkqhkiG9w0BAQsFAAOC 36 | echo.AQEAKS3dMw7fxdPK6n4q61wnVvTcomfJltDzPuGJj5VrRu/CrUq6DLC3Wgdc3US1 37 | echo.wlnlD73QI+6gplL4Zm77LfBILhOmJXnOuW+ktx96bOOhOO5r4V6KX5InpSRBLdWr 38 | echo.3hyj9DpfmZEJsNeuiJktfBUPKfZ+wsKrRkiY8QEgDT/0y0SRF5yIW5JOnyJY9pJ6 39 | echo.iJmd29rMeHSmN4JzNOFrrx+RbvyhSDUmOIl1skLDhtdsW8ttuKkyz+Iu2wxXxSr6 40 | echo.yerkMyqTYXSXs16o1uRo8lEbAnXtV6YIAtK3qbxMZbQUzGkNEiWT7FTasyKC+UlZ 41 | echo.URDYCDWBHebcIogs7r6ouYq5qw== 42 | echo.-----END CERTIFICATE----- 43 | 44 | ) 45 | > C:\chef/trusted_certs/github.pem ( 46 | echo.-----BEGIN CERTIFICATE----- 47 | echo.MIIHeTCCBmGgAwIBAgIQC/20CQrXteZAwwsWyVKaJzANBgkqhkiG9w0BAQsFADB1 48 | echo.MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 49 | echo.d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk 50 | echo.IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE2MDMxMDAwMDAwMFoXDTE4MDUxNzEy 51 | echo.MDAwMFowgf0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB 52 | echo.BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF 53 | echo.Ewc1MTU3NTUwMSQwIgYDVQQJExs4OCBDb2xpbiBQIEtlbGx5LCBKciBTdHJlZXQx 54 | echo.DjAMBgNVBBETBTk0MTA3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p 55 | echo.YTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMu 56 | echo.MRMwEQYDVQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 57 | echo.CgKCAQEA54hc8pZclxgcupjiA/F/OZGRwm/ZlucoQGTNTKmBEgNsrn/mxhngWmPw 58 | echo.bAvUaLP//T79Jc+1WXMpxMiz9PK6yZRRFuIo0d2bx423NA6hOL2RTtbnfs+y0PFS 59 | echo./YTpQSelTuq+Fuwts5v6aAweNyMcYD0HBybkkdosFoDccBNzJ92Ac8I5EVDUc3Or 60 | echo./4jSyZwzxu9kdmBlBzeHMvsqdH8SX9mNahXtXxRpwZnBiUjw36PgN+s9GLWGrafd 61 | echo.02T0ux9Yzd5ezkMxukqEAQ7AKIIijvaWPAJbK/52XLhIy2vpGNylyni/DQD18bBP 62 | echo.T+ZG1uv0QQP9LuY/joO+FKDOTler4wIDAQABo4IDejCCA3YwHwYDVR0jBBgwFoAU 63 | echo.PdNQpdagre7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFIhcSGcZzKB2WS0RecO+oqyH 64 | echo.IidbMCUGA1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1Ud 65 | echo.DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0f 66 | echo.BG4wbDA0oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy 67 | echo.dmVyLWcxLmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTIt 68 | echo.ZXYtc2VydmVyLWcxLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsG 69 | echo.AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGI 70 | echo.BggrBgEFBQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0 71 | echo.LmNvbTBSBggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp 72 | echo.Z2lDZXJ0U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMB 73 | echo.Af8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgCkuQmQtBhYFIe7E6LM 74 | echo.Z3AKPDWYBPkb37jjd80OyA3cEAAAAVNhieoeAAAEAwBHMEUCIQCHHSEY/ROK2/sO 75 | echo.ljbKaNEcKWz6BxHJNPOtjSyuVnSn4QIgJ6RqvYbSX1vKLeX7vpnOfCAfS2Y8lB5R 76 | echo.NMwk6us2QiAAdgBo9pj4H2SCvjqM7rkoHUz8cVFdZ5PURNEKZ6y7T0/7xAAAAVNh 77 | echo.iennAAAEAwBHMEUCIQDZpd5S+3to8k7lcDeWBhiJASiYTk2rNAT26lVaM3xhWwIg 78 | echo.NUqrkIODZpRg+khhp8ag65B8mu0p4JUAmkRDbiYnRvYAdwBWFAaaL9fC7NP14b1E 79 | echo.sj7HRna5vJkRXMDvlJhV1onQ3QAAAVNhieqZAAAEAwBIMEYCIQDnm3WStlvE99GC 80 | echo.izSx+UGtGmQk2WTokoPgo1hfiv8zIAIhAPrYeXrBgseA9jUWWoB4IvmcZtshjXso 81 | echo.nT8MIG1u1zF8MA0GCSqGSIb3DQEBCwUAA4IBAQCLbNtkxuspqycq8h1EpbmAX0wM 82 | echo.5DoW7hM/FVdz4LJ3Kmftyk1yd8j/PSxRrAQN2Mr/frKeK8NE1cMji32mJbBqpWtK 83 | echo./+wC+avPplBUbNpzP53cuTMF/QssxItPGNP5/OT9Aj1BxA/NofWZKh4ufV7cz3pY 84 | echo.RDS4BF+EEFQ4l5GY+yp4WJA/xSvYsTHWeWxRD1/nl62/Rd9FN2NkacRVozCxRVle 85 | echo.FrBHTFxqIP6kDnxiLElBrZngtY07ietaYZVLQN/ETyqLQftsf8TecwTklbjvm8NT 86 | echo.JqbaIVifYwqwNN+4lRxS3F5lNlA/il12IOgbRioLI62o8G0DaEUQgHNf8vSG 87 | echo.-----END CERTIFICATE----- 88 | 89 | ) 90 | -------------------------------------------------------------------------------- /spec/data/client.d_00/00-foo.rb: -------------------------------------------------------------------------------- 1 | # 00-foo.rb 2 | # d6f9b976-289c-4149-baf7-81e6ffecf228 3 | -------------------------------------------------------------------------------- /spec/data/client.d_00/foo/bar.rb: -------------------------------------------------------------------------------- 1 | 1 / 0 2 | -------------------------------------------------------------------------------- /spec/data/client.d_01/foo/bar.rb: -------------------------------------------------------------------------------- 1 | 1 / 0 2 | -------------------------------------------------------------------------------- /spec/dummy_winrm_connection.rb: -------------------------------------------------------------------------------- 1 | module Dummy 2 | class WinRMTransport 3 | attr_reader :httpcli 4 | 5 | def initialize 6 | @httpcli = HTTPClient.new 7 | end 8 | end 9 | 10 | class Connection 11 | attr_reader :transport 12 | attr_accessor :logger 13 | 14 | def initialize 15 | @transport = WinRMTransport.new 16 | end 17 | 18 | def shell; end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | 2 | # Author:: Adam Edwards () 3 | # Copyright:: Copyright (c) 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 15 | # implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | def sample_data(file_name) 21 | file = File.expand_path(File.dirname("spec/assets/*")) + "/#{file_name}" 22 | File.read(file) 23 | end 24 | 25 | class UnexpectedSystemExit < RuntimeError 26 | def self.from(system_exit) 27 | new(system_exit.message).tap { |e| e.set_backtrace(system_exit.backtrace) } 28 | end 29 | end 30 | 31 | RSpec.configure do |config| 32 | config.raise_on_warning = true 33 | config.raise_errors_for_deprecations! 34 | config.run_all_when_everything_filtered = true 35 | config.filter_run focus: true 36 | config.around(:example) do |ex| 37 | 38 | ex.run 39 | rescue SystemExit => e 40 | raise UnexpectedSystemExit.from(e) 41 | 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/unit/knife/windows_cert_generate_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Mukta Aphale 3 | # Copyright:: Copyright (c) 2014-2016 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 "spec_helper" 20 | require "chef/knife/windows_cert_generate" 21 | 22 | describe Chef::Knife::WindowsCertGenerate do 23 | before(:all) do 24 | @certgen = Chef::Knife::WindowsCertGenerate.new(["-H", "something.mydomain.com"]) 25 | end 26 | 27 | it "generates RSA key pair" do 28 | @certgen.config[:key_length] = 2048 29 | key = @certgen.generate_keypair 30 | expect(key).to be_instance_of OpenSSL::PKey::RSA 31 | end 32 | 33 | it "generates X509 certificate" do 34 | @certgen.config[:domain] = "test.com" 35 | @certgen.config[:cert_validity] = "24" 36 | key = @certgen.generate_keypair 37 | certificate = @certgen.generate_certificate key 38 | expect(certificate).to be_instance_of OpenSSL::X509::Certificate 39 | end 40 | 41 | it "writes certificate to file" do 42 | expect(File).to receive(:open).exactly(3).times 43 | store_key = false 44 | cert = double(OpenSSL::X509::Certificate.new) 45 | key = double(OpenSSL::PKey::RSA.new) 46 | @certgen.config[:cert_passphrase] = "password" 47 | expect(OpenSSL::PKCS12).to receive(:create).with("password", "winrmcert", key, cert) 48 | @certgen.write_certificate_to_file cert, "winrmcert", key, store_key 49 | end 50 | 51 | context "when creating certificate files" do 52 | before do 53 | @certgen.thumbprint = "TEST_THUMBPRINT" 54 | allow(Dir).to receive(:glob).and_return([]) 55 | allow(@certgen).to receive(:generate_keypair) 56 | allow(@certgen).to receive(:generate_certificate) 57 | expect(@certgen.ui).to receive(:info).with("Generated Certificates:") 58 | expect(@certgen.ui).to receive(:info).with("- winrmcert.pfx - PKCS12 format key pair. Contains public and private keys, can be used with an SSL server.") 59 | expect(@certgen.ui).to receive(:info).with("- winrmcert.b64 - Base64 encoded PKCS12 key pair. Contains public and private keys, used by some cloud provider API's to configure SSL servers.") 60 | expect(@certgen.ui).to receive(:info).with("- winrmcert.pem - Base64 encoded public certificate only. Required by the client to connect to the server.") 61 | expect(@certgen.ui).to receive(:info).with("Certificate Thumbprint: TEST_THUMBPRINT") 62 | end 63 | 64 | it "writes out certificates" do 65 | @certgen.config[:output_file] = "winrmcert" 66 | 67 | expect(@certgen).to receive(:certificates_already_exist?).and_return(false) 68 | expect(@certgen).to receive(:write_certificate_to_file) 69 | @certgen.run 70 | end 71 | 72 | it "prompts when certificates already exist" do 73 | file_path = "winrmcert" 74 | @certgen.config[:output_file] = file_path 75 | 76 | allow(File).to receive(:exist?).and_return([file_path]) 77 | expect(@certgen).to receive(:confirm).with("Do you really want to overwrite existing certificates") 78 | expect(@certgen).to receive(:write_certificate_to_file) 79 | @certgen.run 80 | end 81 | 82 | it "creates certificate on specified file path" do 83 | file_path = "/tmp/winrmcert" 84 | @certgen.name_args = [file_path] 85 | 86 | expect(@certgen).to receive(:write_certificate_to_file) # FIXME: this should be testing that we get /tmp/winrmcert as the filename 87 | @certgen.run 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/unit/knife/windows_cert_install_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Mukta Aphale 3 | # Copyright:: Copyright (c) 2014-2016 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 "spec_helper" 20 | require "chef/knife/windows_cert_install" 21 | 22 | describe Chef::Knife::WindowsCertInstall do 23 | context "on Windows" do 24 | before do 25 | allow(Chef::Platform).to receive(:windows?).and_return(true) 26 | @certinstall = Chef::Knife::WindowsCertInstall.new 27 | end 28 | 29 | it "installs certificate" do 30 | @certinstall.name_args = ["test-path"] 31 | @certinstall.config[:cert_passphrase] = "your-secret!" 32 | allow(Chef::Platform).to receive(:windows?).and_return(true) 33 | expect(@certinstall).to receive(:`).with("powershell.exe -Command \" 'your-secret!' | certutil -importPFX 'test-path' AT_KEYEXCHANGE\"") 34 | expect(@certinstall.ui).to receive(:info).with("Certificate added to Certificate Store") 35 | expect(@certinstall.ui).to receive(:info).with("Adding certificate to the Windows Certificate Store...") 36 | @certinstall.run 37 | end 38 | end 39 | 40 | context "not on Windows" do 41 | before do 42 | allow(Chef::Platform).to receive(:windows?).and_return(false) 43 | @listener = Chef::Knife::WindowsListenerCreate.new 44 | end 45 | 46 | it "exits with an error" do 47 | expect(@listener.ui).to receive(:error) 48 | expect { @listener.run }.to raise_error(SystemExit) 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/unit/knife/windows_listener_create_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Mukta Aphale 3 | # Copyright:: Copyright (c) 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 "spec_helper" 20 | require "chef/knife/windows_listener_create" 21 | 22 | describe Chef::Knife::WindowsListenerCreate do 23 | context "on Windows" do 24 | before do 25 | allow(Chef::Platform).to receive(:windows?).and_return(true) 26 | @listener = Chef::Knife::WindowsListenerCreate.new 27 | end 28 | 29 | it "creates winrm listener" do 30 | @listener.config[:hostname] = "host" 31 | @listener.config[:cert_thumbprint] = "CERT-THUMBPRINT" 32 | @listener.config[:port] = "5986" 33 | expect(@listener).to receive(:`).with("winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=\"host\";CertificateThumbprint=\"CERT-THUMBPRINT\";Port=\"5986\"}") 34 | expect(@listener.ui).to receive(:info).with("WinRM listener created with Port: 5986 and CertificateThumbprint: CERT-THUMBPRINT") 35 | @listener.run 36 | end 37 | 38 | it "raise an error on command failure" do 39 | @listener.config[:hostname] = "host" 40 | @listener.config[:cert_thumbprint] = "CERT-THUMBPRINT" 41 | @listener.config[:port] = "5986" 42 | @listener.config[:basic_auth] = true 43 | expect(@listener).to receive(:`).with("winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=\"host\";CertificateThumbprint=\"CERT-THUMBPRINT\";Port=\"5986\"}") 44 | expect(@listener).to receive(:exitstatus).and_return(100) 45 | expect(@listener.ui).to receive(:error).with("Error creating WinRM listener. use -VV for more details.") 46 | expect(@listener.ui).to_not receive(:info).with("WinRM listener created with Port: 5986 and CertificateThumbprint: CERT-THUMBPRINT") 47 | expect { @listener.run }.to raise_error(SystemExit) 48 | end 49 | 50 | it "creates winrm listener with cert install option" do 51 | @listener.config[:hostname] = "host" 52 | @listener.config[:cert_thumbprint] = "CERT-THUMBPRINT" 53 | @listener.config[:port] = "5986" 54 | @listener.config[:cert_install] = true 55 | allow(@listener).to receive(:get_cert_passphrase).and_return("your-secret!") 56 | expect(@listener).to receive(:`).with("powershell.exe -Command \" 'your-secret!' | certutil -importPFX 'true' AT_KEYEXCHANGE\"") 57 | expect(@listener).to receive(:`).with("powershell.exe -Command \" echo (Get-PfxCertificate true).thumbprint \"") 58 | expect(@listener.ui).to receive(:info).with("Certificate installed to Certificate Store") 59 | expect(@listener.ui).to receive(:info).with("Certificate Thumbprint: ") 60 | allow(@listener).to receive(:puts) 61 | @listener.run 62 | end 63 | end 64 | 65 | context "not on Windows" do 66 | before do 67 | allow(Chef::Platform).to receive(:windows?).and_return(false) 68 | @listener = Chef::Knife::WindowsListenerCreate.new 69 | end 70 | 71 | it "exits with an error" do 72 | expect(@listener.ui).to receive(:error) 73 | expect { @listener.run }.to raise_error(SystemExit) 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/unit/knife/winrm_session_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Steven Murawski 3 | # Copyright:: Copyright (c) 2015-2020 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 "spec_helper" 20 | require_relative "../../../lib/chef/knife/helpers/winrm_session" 21 | require "dummy_winrm_connection" 22 | 23 | describe Chef::Knife::WinrmSession do 24 | let(:winrm_connection) { Dummy::Connection.new } 25 | let(:options) { { transport: :plaintext } } 26 | 27 | before do 28 | @original_config = Chef::Config.hash_dup 29 | allow(WinRM::Connection).to receive(:new).and_return(winrm_connection) 30 | end 31 | 32 | after do 33 | Chef::Config.configuration = @original_config 34 | end 35 | 36 | subject { Chef::Knife::WinrmSession.new(options) } 37 | 38 | describe "#initialize" do 39 | context "when a proxy is configured" do 40 | let(:proxy_uri) { "blah.com" } 41 | let(:ssl_policy) { double("DefaultSSLPolicy", set_custom_certs: nil) } 42 | 43 | before do 44 | Chef::Config[:http_proxy] = proxy_uri 45 | end 46 | 47 | it "sets the http_proxy to the configured proxy" do 48 | subject 49 | expect(ENV["HTTP_PROXY"]).to eq("http://#{proxy_uri}") 50 | end 51 | 52 | it "sets the ssl policy on the winrm client" do 53 | expect(Chef::HTTP::DefaultSSLPolicy).to receive(:new) 54 | .with(winrm_connection.transport.httpcli.ssl_config) 55 | .and_return(ssl_policy) 56 | expect(ssl_policy).to receive(:set_custom_certs) 57 | subject 58 | end 59 | 60 | end 61 | end 62 | 63 | describe "#relay_command" do 64 | it "run command and display commands output" do 65 | expect(winrm_connection).to receive(:shell) 66 | subject.relay_command("cmd.exe echo 'hi'") 67 | end 68 | 69 | it "exits with 401 if command execution raises a 401" do 70 | expect(winrm_connection).to receive(:shell).and_raise(WinRM::WinRMHTTPTransportError.new("", "401")) 71 | expect { subject.relay_command("cmd.exe echo 'hi'") }.to raise_error(WinRM::WinRMHTTPTransportError) 72 | expect(subject.exit_code).to eql(401) 73 | end 74 | 75 | context "cmd shell" do 76 | before do 77 | options[:shell] = :cmd 78 | options[:codepage] = 65001 79 | end 80 | 81 | it "creates shell and sends codepage" do 82 | expect(winrm_connection).to receive(:shell).with(:cmd, hash_including(codepage: 65001)) 83 | subject.relay_command("cmd.exe echo 'hi'") 84 | end 85 | end 86 | 87 | context "powershell shell" do 88 | before do 89 | options[:shell] = :powershell 90 | options[:codepage] = 65001 91 | end 92 | 93 | it "does not send codepage to shell" do 94 | expect(winrm_connection).to receive(:shell).with(:powershell) 95 | subject.relay_command("cmd.exe echo 'hi'") 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /spec/unit/knife/winrm_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Bryan McLellan 3 | # Copyright:: Copyright (c) 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 "spec_helper" 20 | require_relative "../../../lib/chef/knife/winrm" 21 | require "dummy_winrm_connection" 22 | 23 | describe Chef::Knife::Winrm do 24 | before do 25 | Chef::Config.reset 26 | end 27 | 28 | describe "#resolve_target_nodes" do 29 | before do 30 | @knife = Chef::Knife::Winrm.new 31 | @knife.config[:attribute] = "fqdn" 32 | @node_foo = Chef::Node.new 33 | @node_foo.automatic_attrs[:fqdn] = "foo.example.org" 34 | @node_foo.automatic_attrs[:ipaddress] = "10.0.0.1" 35 | @node_bar = Chef::Node.new 36 | @node_bar.automatic_attrs[:fqdn] = "bar.example.org" 37 | @node_bar.automatic_attrs[:ipaddress] = "10.0.0.2" 38 | @node_bar.automatic_attrs[:ec2][:public_hostname] = "somewhere.com" 39 | @query = double("Chef::Search::Query") 40 | end 41 | 42 | context "when there are some hosts found but they do not have an attribute to connect with" do 43 | before do 44 | @knife.config[:manual] = false 45 | @knife.config[:winrm_password] = "P@ssw0rd!" 46 | allow(@query).to receive(:search).and_return([[@node_foo, @node_bar]]) 47 | @node_foo.automatic_attrs[:fqdn] = nil 48 | @node_bar.automatic_attrs[:fqdn] = nil 49 | allow(Chef::Search::Query).to receive(:new).and_return(@query) 50 | end 51 | 52 | it "raises a specific error (KNIFE-222)" do 53 | expect(@knife.ui).to receive(:fatal).with(/does not have the required attribute/) 54 | expect(@knife).to receive(:exit).with(10) 55 | @knife.configure_chef 56 | @knife.resolve_target_nodes 57 | end 58 | end 59 | 60 | context "when there are nested attributes" do 61 | before do 62 | @knife.config[:manual] = false 63 | @knife.config[:winrm_password] = "P@ssw0rd!" 64 | allow(@query).to receive(:search).and_return([[@node_foo, @node_bar]]) 65 | allow(Chef::Search::Query).to receive(:new).and_return(@query) 66 | end 67 | 68 | it "uses the nested attributes (KNIFE-276)" do 69 | @knife.config[:attribute] = "ec2.public_hostname" 70 | @knife.configure_chef 71 | @knife.resolve_target_nodes 72 | end 73 | end 74 | end 75 | 76 | describe "#configure_session" do 77 | let(:winrm_user) { "testuser" } 78 | let(:transport) { "plaintext" } 79 | let(:password) { "testpassword" } 80 | let(:protocol) { "basic" } 81 | let(:knife_args) do 82 | [ 83 | "-m", "localhost", 84 | "-x", winrm_user, 85 | "-P", password, 86 | "-w", transport, 87 | "--winrm-authentication-protocol", protocol, 88 | "echo helloworld" 89 | ] 90 | end 91 | let(:winrm_session) { double("winrm_session") } 92 | let(:winrm_connection) { Dummy::Connection.new } 93 | 94 | subject { Chef::Knife::Winrm.new(knife_args) } 95 | 96 | context "when configuring the WinRM user name" do 97 | context "when basic auth is used" do 98 | let(:protocol) { "basic" } 99 | 100 | it "passes user name as given in options" do 101 | expect(Chef::Knife::WinrmSession).to receive(:new) do |opts| 102 | expect(opts[:user]).to eq(winrm_user) 103 | end.and_return(winrm_session) 104 | subject.configure_session 105 | end 106 | end 107 | 108 | context "when negotiate auth is used" do 109 | let(:protocol) { "negotiate" } 110 | 111 | context "when user is prefixed with realm" do 112 | let(:winrm_user) { "my_realm\\myself" } 113 | 114 | it "passes user name as given in options" do 115 | expect(Chef::Knife::WinrmSession).to receive(:new) do |opts| 116 | expect(opts[:user]).to eq(winrm_user) 117 | end.and_return(winrm_session) 118 | subject.configure_session 119 | end 120 | end 121 | 122 | context "when user realm is included via email format" do 123 | let(:winrm_user) { "myself@my_realm.com" } 124 | 125 | it "passes user name as given in options" do 126 | expect(Chef::Knife::WinrmSession).to receive(:new) do |opts| 127 | expect(opts[:user]).to eq(winrm_user) 128 | end.and_return(winrm_session) 129 | subject.configure_session 130 | end 131 | end 132 | 133 | context "when a local user is given" do 134 | it "prefixes user with the dot (local) realm" do 135 | expect(Chef::Knife::WinrmSession).to receive(:new) do |opts| 136 | expect(opts[:user]).to eq(".\\#{winrm_user}") 137 | end.and_return(winrm_session) 138 | subject.configure_session 139 | end 140 | end 141 | end 142 | end 143 | 144 | context "when configuring the WinRM password" do 145 | it "passes password as given in options" do 146 | expect(Chef::Knife::WinrmSession).to receive(:new) do |opts| 147 | expect(opts[:password]).to eq(password) 148 | end.and_return(winrm_session) 149 | subject.configure_session 150 | end 151 | 152 | context "when no password is given in the options" do 153 | let(:knife_args) do 154 | [ 155 | "-m", "localhost", 156 | "-x", winrm_user, 157 | "-w", transport, 158 | "--winrm-authentication-protocol", protocol, 159 | "echo helloworld" 160 | ] 161 | end 162 | let(:prompted_password) { "prompted_password" } 163 | 164 | before do 165 | allow(subject.ui).to receive(:ask).and_return(prompted_password) 166 | end 167 | 168 | it "passes password prompted" do 169 | expect(Chef::Knife::WinrmSession).to receive(:new) do |opts| 170 | expect(opts[:password]).to eq(prompted_password) 171 | end.and_return(winrm_session) 172 | subject.configure_session 173 | end 174 | end 175 | end 176 | 177 | context "when configuring the WinRM transport" do 178 | context "kerberos option is set" do 179 | let(:winrm_command_http) { 180 | Chef::Knife::Winrm.new([ 181 | "-m", "localhost", 182 | "-x", "testuser", 183 | "-P", "testpassword", 184 | "--winrm-authentication-protocol", "basic", 185 | "--kerberos-realm", "realm", 186 | "echo helloworld" 187 | ]) 188 | } 189 | 190 | it "sets the transport to kerberos" do 191 | expect(WinRM::Connection).to receive(:new).with(hash_including(transport: :kerberos)).and_return(winrm_connection) 192 | winrm_command_http.configure_chef 193 | winrm_command_http.configure_session 194 | end 195 | end 196 | 197 | context "kerberos option is set but nil" do 198 | let(:winrm_command_http) { 199 | Chef::Knife::Winrm.new([ 200 | "-m", "localhost", 201 | "-x", "testuser", 202 | "-P", "testpassword", 203 | "--winrm-authentication-protocol", "basic", 204 | "echo helloworld" 205 | ]) 206 | } 207 | 208 | it "sets the transport to plaintext" do 209 | winrm_command_http.config[:kerberos_realm] = nil 210 | expect(WinRM::Connection).to receive(:new).with(hash_including(transport: :plaintext)).and_return(winrm_connection) 211 | winrm_command_http.configure_chef 212 | winrm_command_http.configure_session 213 | end 214 | end 215 | 216 | context "on windows workstations" do 217 | let(:protocol) { "negotiate" } 218 | 219 | before do 220 | allow(Chef::Platform).to receive(:windows?).and_return(true) 221 | end 222 | 223 | it "defaults to negotiate when on a Windows host" do 224 | expect(Chef::Knife::WinrmSession).to receive(:new) do |opts| 225 | expect(opts[:transport]).to eq(:negotiate) 226 | end.and_return(winrm_session) 227 | subject.configure_session 228 | end 229 | end 230 | 231 | context "on non-windows workstations" do 232 | before do 233 | allow(Chef::Platform).to receive(:windows?).and_return(false) 234 | end 235 | 236 | let(:winrm_command_http) { Chef::Knife::Winrm.new(["-m", "localhost", "-x", "testuser", "-P", "testpassword", "-w", "plaintext", "--winrm-authentication-protocol", "basic", "echo helloworld"]) } 237 | 238 | it "defaults to the http uri scheme" do 239 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :plaintext)).and_call_original 240 | expect(WinRM::Connection).to receive(:new).with(hash_including(endpoint: "http://localhost:5985/wsman")).and_return(winrm_connection) 241 | winrm_command_http.configure_chef 242 | winrm_command_http.configure_session 243 | end 244 | 245 | it "sets the operation timeout and verifes default" do 246 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(operation_timeout: 1800)).and_call_original 247 | expect(WinRM::Connection).to receive(:new).with(hash_including(operation_timeout: 1800)).and_return(winrm_connection) 248 | winrm_command_http.configure_chef 249 | winrm_command_http.configure_session 250 | end 251 | 252 | it "sets the user specified winrm port" do 253 | winrm_command_http.config[:knife] = { winrm_port: "5988" } 254 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :plaintext)).and_call_original 255 | expect(WinRM::Connection).to receive(:new).with(hash_including(transport: :plaintext)).and_return(winrm_connection) 256 | winrm_command_http.configure_chef 257 | winrm_command_http.configure_session 258 | end 259 | 260 | let(:winrm_command_timeout) { Chef::Knife::Winrm.new(["-m", "localhost", "-x", "testuser", "-P", "testpassword", "--winrm-authentication-protocol", "basic", "--session-timeout", "10", "echo helloworld"]) } 261 | 262 | it "sets operation timeout and verify 10 Minute timeout" do 263 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(operation_timeout: 600)).and_call_original 264 | expect(WinRM::Connection).to receive(:new).with(hash_including(operation_timeout: 600)).and_return(winrm_connection) 265 | winrm_command_timeout.configure_chef 266 | winrm_command_timeout.configure_session 267 | end 268 | 269 | let(:winrm_command_https) { Chef::Knife::Winrm.new(["-m", "localhost", "-x", "testuser", "-P", "testpassword", "--winrm-transport", "ssl", "echo helloworld"]) } 270 | 271 | it "uses the https uri scheme if the ssl transport is specified" do 272 | winrm_command_http.config[:winrm_transport] = "ssl" 273 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :ssl)).and_call_original 274 | expect(WinRM::Connection).to receive(:new).with(hash_including(endpoint: "https://localhost:5986/wsman")).and_return(winrm_connection) 275 | winrm_command_https.configure_chef 276 | winrm_command_https.configure_session 277 | end 278 | 279 | it "uses the winrm port '5986' by default for ssl transport" do 280 | winrm_command_http.config[:winrm_transport] = "ssl" 281 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :ssl)).and_call_original 282 | expect(WinRM::Connection).to receive(:new).with(hash_including(endpoint: "https://localhost:5986/wsman")).and_return(winrm_connection) 283 | winrm_command_https.configure_chef 284 | winrm_command_https.configure_session 285 | end 286 | 287 | it "defaults to validating the server when the ssl transport is used" do 288 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :ssl)).and_call_original 289 | expect(WinRM::Connection).to receive(:new).with(hash_including(no_ssl_peer_verification: false)).and_return(winrm_connection) 290 | winrm_command_https.configure_chef 291 | winrm_command_https.configure_session 292 | end 293 | 294 | let(:winrm_command_verify_peer) { Chef::Knife::Winrm.new(["-m", "localhost", "-x", "testuser", "-P", "testpassword", "--winrm-transport", "ssl", "--winrm-ssl-verify-mode", "verify_peer", "echo helloworld"]) } 295 | 296 | it "validates the server when the ssl transport is used and the :winrm_ssl_verify_mode option is not configured to :verify_none" do 297 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :ssl)).and_call_original 298 | expect(WinRM::Connection).to receive(:new).with(hash_including(no_ssl_peer_verification: false)).and_return(winrm_connection) 299 | winrm_command_verify_peer.configure_chef 300 | winrm_command_verify_peer.configure_session 301 | end 302 | 303 | context "when setting verify_none" do 304 | let(:transport) { "ssl" } 305 | 306 | before { knife_args << "--winrm-ssl-verify-mode" << "verify_none" } 307 | 308 | it "does not validate the server when the ssl transport is used and the :winrm_ssl_verify_mode option is set to :verify_none" do 309 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :ssl)).and_call_original 310 | expect(WinRM::Connection).to receive(:new).with(hash_including(no_ssl_peer_verification: true)).and_return(winrm_connection) 311 | subject.configure_chef 312 | subject.configure_session 313 | end 314 | 315 | it "prints warning output when the :winrm_ssl_verify_mode set to :verify_none to disable server validation" do 316 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :ssl)).and_call_original 317 | expect(WinRM::Connection).to receive(:new).with(hash_including(no_ssl_peer_verification: true)).and_return(winrm_connection) 318 | expect(subject).to receive(:warn_no_ssl_peer_verification) 319 | 320 | subject.configure_chef 321 | subject.configure_session 322 | end 323 | 324 | context "when transport is plaintext" do 325 | let(:transport) { "plaintext" } 326 | 327 | it "does not print warning re ssl server validation" do 328 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :plaintext)).and_call_original 329 | expect(WinRM::Connection).to receive(:new).and_return(winrm_connection) 330 | expect(subject).to_not receive(:warn_no_ssl_peer_verification) 331 | 332 | subject.configure_chef 333 | subject.configure_session 334 | end 335 | end 336 | end 337 | 338 | let(:winrm_command_ca_trust) { Chef::Knife::Winrm.new(["-m", "localhost", "-x", "testuser", "-P", "testpassword", "--winrm-transport", "ssl", "--ca-trust-file", "~/catrustroot", "--winrm-ssl-verify-mode", "verify_none", "echo helloworld"]) } 339 | 340 | it "validates the server when the ssl transport is used and the :ca_trust_file option is specified even if the :winrm_ssl_verify_mode option is set to :verify_none" do 341 | expect(Chef::Knife::WinrmSession).to receive(:new).with(hash_including(transport: :ssl)).and_call_original 342 | expect(WinRM::Connection).to receive(:new).with(hash_including(no_ssl_peer_verification: false)).and_return(winrm_connection) 343 | winrm_command_ca_trust.configure_chef 344 | winrm_command_ca_trust.configure_session 345 | end 346 | end 347 | end 348 | end 349 | 350 | describe "#run" do 351 | let(:session_opts) do 352 | { 353 | user: ".\\testuser", 354 | password: "testpassword", 355 | port: "5985", 356 | transport: :plaintext, 357 | host: "localhost", 358 | } 359 | end 360 | let(:session) { Chef::Knife::WinrmSession.new(session_opts) } 361 | 362 | before(:each) do 363 | allow(Chef::Knife::WinrmSession).to receive(:new).and_return(session) 364 | @winrm = Chef::Knife::Winrm.new(["-m", "localhost", "-x", "testuser", "-P", "testpassword", "--winrm-authentication-protocol", "basic", "echo helloworld"]) 365 | @winrm.config[:winrm_transport] = "plaintext" 366 | end 367 | 368 | it "returns with 0 if the command succeeds" do 369 | allow(@winrm).to receive(:relay_winrm_command).and_return(0) 370 | return_code = @winrm.run 371 | expect(return_code).to be_zero 372 | end 373 | 374 | it "exits with exact exit status if the command fails and returns config is set to 0" do 375 | command_status = 510 376 | 377 | @winrm.config[:returns] = "0" 378 | 379 | allow(@winrm).to receive(:relay_winrm_command) 380 | allow(@winrm.ui).to receive(:error) 381 | allow(session).to receive(:exit_code).and_return(command_status) 382 | expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(command_status) } 383 | end 384 | 385 | it "exits with non-zero status if the command fails and returns config is set to 0" do 386 | command_status = 1 387 | @winrm.config[:returns] = "0,53" 388 | allow(@winrm).to receive(:relay_winrm_command).and_return(command_status) 389 | allow(@winrm.ui).to receive(:error) 390 | allow(session).to receive(:exit_code).and_return(command_status) 391 | expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(command_status) } 392 | end 393 | 394 | it "exits with a zero status if the command returns an expected non-zero status" do 395 | command_status = 53 396 | @winrm.config[:returns] = "0,53" 397 | allow(@winrm).to receive(:relay_winrm_command).and_return(command_status) 398 | allow(session).to receive(:exit_codes).and_return({ "thishost" => command_status }) 399 | exit_code = @winrm.run 400 | expect(exit_code).to be_zero 401 | end 402 | 403 | it "exits with a zero status if the command returns an expected non-zero status" do 404 | command_status = 53 405 | @winrm.config[:returns] = "0,53" 406 | allow(@winrm).to receive(:relay_winrm_command).and_return(command_status) 407 | allow(session).to receive(:exit_codes).and_return({ "thishost" => command_status }) 408 | exit_code = @winrm.run 409 | expect(exit_code).to be_zero 410 | end 411 | 412 | it "exits with 100 and no hint if command execution raises an exception other than 401" do 413 | allow(@winrm).to receive(:relay_winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new("", "500")) 414 | allow(@winrm.ui).to receive(:error) 415 | expect(@winrm.ui).to_not receive(:info) 416 | expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(100) } 417 | end 418 | 419 | it "exits with 100 if command execution raises a 401" do 420 | allow(@winrm).to receive(:relay_winrm_command).and_raise(WinRM::WinRMHTTPTransportError.new("", "401")) 421 | allow(@winrm.ui).to receive(:info) 422 | allow(@winrm.ui).to receive(:error) 423 | expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) { |e| expect(e.status).to eq(100) } 424 | end 425 | 426 | it "prints a hint on failure for negotiate authentication" do 427 | @winrm.config[:winrm_authentication_protocol] = "negotiate" 428 | @winrm.config[:winrm_transport] = "plaintext" 429 | allow(Chef::Platform).to receive(:windows?).and_return(true) 430 | allow(session).to receive(:relay_command).and_raise(WinRM::WinRMAuthorizationError.new) 431 | allow(@winrm.ui).to receive(:error) 432 | allow(@winrm.ui).to receive(:info) 433 | expect(@winrm.ui).to receive(:info).with(Chef::Knife::Winrm::FAILED_NOT_BASIC_HINT) 434 | expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) 435 | end 436 | 437 | it "prints a hint on failure for basic authentication" do 438 | @winrm.config[:winrm_authentication_protocol] = "basic" 439 | @winrm.config[:winrm_transport] = "plaintext" 440 | allow(session).to receive(:relay_command).and_raise(WinRM::WinRMHTTPTransportError.new("", "401")) 441 | allow(@winrm.ui).to receive(:error) 442 | allow(@winrm.ui).to receive(:info) 443 | expect(@winrm.ui).to receive(:info).with(Chef::Knife::Winrm::FAILED_BASIC_HINT) 444 | expect { @winrm.run_with_pretty_exceptions }.to raise_error(SystemExit) 445 | end 446 | 447 | context "when winrm_authentication_protocol specified" do 448 | before do 449 | @winrm.config[:winrm_transport] = "plaintext" 450 | allow(@winrm).to receive(:relay_winrm_command).and_return(0) 451 | end 452 | 453 | it "sets negotiate transport on windows for 'negotiate' authentication" do 454 | @winrm.config[:winrm_authentication_protocol] = "negotiate" 455 | allow(Chef::Platform).to receive(:windows?).and_return(true) 456 | allow(Chef::Knife::WinrmSession).to receive(:new) do |opts| 457 | expect(opts[:disable_sspi]).to be(false) 458 | expect(opts[:transport]).to be(:negotiate) 459 | end.and_return(session) 460 | @winrm.run 461 | end 462 | 463 | it "sets negotiate transport on unix for 'negotiate' authentication" do 464 | @winrm.config[:winrm_authentication_protocol] = "negotiate" 465 | allow(Chef::Platform).to receive(:windows?).and_return(false) 466 | allow(Chef::Knife::WinrmSession).to receive(:new) do |opts| 467 | expect(opts[:disable_sspi]).to be(false) 468 | expect(opts[:transport]).to be(:negotiate) 469 | end.and_return(session) 470 | @winrm.run 471 | end 472 | 473 | it "disables sspi and skips the winrm monkey patch for 'ssl' transport and 'basic' authentication" do 474 | @winrm.config[:winrm_authentication_protocol] = "basic" 475 | @winrm.config[:winrm_transport] = "ssl" 476 | @winrm.config[:winrm_port] = "5986" 477 | allow(Chef::Platform).to receive(:windows?).and_return(true) 478 | allow(Chef::Knife::WinrmSession).to receive(:new) do |opts| 479 | expect(opts[:port]).to be(@winrm.config[:winrm_port]) 480 | expect(opts[:transport]).to be(:ssl) 481 | expect(opts[:disable_sspi]).to be(true) 482 | expect(opts[:basic_auth_only]).to be(true) 483 | end.and_return(session) 484 | @winrm.run 485 | end 486 | 487 | it "raises an error if value is other than [basic, negotiate, kerberos]" do 488 | @winrm.config[:winrm_authentication_protocol] = "invalid" 489 | allow(Chef::Platform).to receive(:windows?).and_return(true) 490 | expect(@winrm.ui).to receive(:error) 491 | expect { @winrm.run }.to raise_error(SystemExit) 492 | end 493 | end 494 | end 495 | 496 | context "Impact of concurrency value when target nodes are 3" do 497 | let(:winrm_user) { "testuser" } 498 | let(:transport) { "plaintext" } 499 | let(:password) { "testpassword" } 500 | let(:protocol) { "basic" } 501 | let(:knife_args) do 502 | [ 503 | "-m", "localhost knownhost somehost", 504 | "-x", winrm_user, 505 | "-P", password, 506 | "-w", transport, 507 | "--winrm-authentication-protocol", protocol, 508 | "echo helloworld" 509 | ] 510 | end 511 | let(:winrm_connection) { Dummy::Connection.new } 512 | 513 | subject { Chef::Knife::Winrm.new(knife_args) } 514 | 515 | context "when concurrency limit is not set" do 516 | it "spawns a number of connection threads equal to the number of target nodes" do 517 | allow(subject).to receive(:run_command_in_thread).and_return("echo helloworld") 518 | expect(Thread).to receive(:new).exactly(3).times.and_call_original 519 | subject.configure_session 520 | subject.relay_winrm_command(knife_args.last) 521 | end 522 | end 523 | 524 | context "when concurrency limit is set" do 525 | it "starts only the required number of threads when there are fewer targets than threads" do 526 | knife_args.push("-C", "4") 527 | allow(subject).to receive(:run_command_in_thread).and_return("echo helloworld") 528 | expect(Thread).to receive(:new).exactly(3).times.and_call_original 529 | subject.configure_session 530 | subject.relay_winrm_command(knife_args.last) 531 | end 532 | it "starts only the requested number of threads when there are as many targets as threads" do 533 | knife_args.push("-C", "3") 534 | allow(subject).to receive(:run_command_in_thread).and_return("echo helloworld") 535 | expect(Thread).to receive(:new).exactly(3).times.and_call_original 536 | subject.configure_session 537 | subject.relay_winrm_command(knife_args.last) 538 | end 539 | it "starts only the requested number of threads when there are more targets then threads" do 540 | knife_args.push("-C", "2") 541 | allow(subject).to receive(:run_command_in_thread).and_return("echo helloworld") 542 | expect(Thread).to receive(:new).exactly(2).times.and_call_original 543 | subject.configure_session 544 | subject.relay_winrm_command(knife_args.last) 545 | end 546 | end 547 | 548 | context "should call run_command_in_thread thrice when" do 549 | it "concurrency not set" do 550 | expect(subject).to receive(:run_command_in_thread).thrice 551 | subject.configure_session 552 | subject.relay_winrm_command(knife_args.last) 553 | end 554 | it "concurrency set to 4" do 555 | knife_args.push("-C", "4") 556 | expect(subject).to receive(:run_command_in_thread).thrice 557 | subject.configure_session 558 | subject.relay_winrm_command(knife_args.last) 559 | end 560 | it "concurrency set to 3" do 561 | knife_args.push("-C", "3") 562 | expect(subject).to receive(:run_command_in_thread).thrice 563 | subject.configure_session 564 | subject.relay_winrm_command(knife_args.last) 565 | end 566 | it "concurrency set to 2" do 567 | knife_args.push("-C", "2") 568 | expect(subject).to receive(:run_command_in_thread).thrice 569 | subject.configure_session 570 | subject.relay_winrm_command(knife_args.last) 571 | end 572 | it "concurrency set to 1" do 573 | knife_args.push("-C", "1") 574 | expect(subject).to receive(:run_command_in_thread).thrice 575 | subject.configure_session 576 | subject.relay_winrm_command(knife_args.last) 577 | end 578 | it "concurrency set to 0" do 579 | knife_args.push("-C", "0") 580 | expect(subject).to receive(:run_command_in_thread).thrice 581 | subject.configure_session 582 | subject.relay_winrm_command(knife_args.last) 583 | end 584 | end 585 | end 586 | end 587 | -------------------------------------------------------------------------------- /spec/unit/knife/wsman_test_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Steven Murawski () 3 | # Copyright:: Copyright (c) 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 "spec_helper" 20 | require_relative "../../../lib/chef/knife/wsman_test" 21 | 22 | describe Chef::Knife::WsmanTest do 23 | let(:http_client_mock) { HTTPClient.new } 24 | let(:ssl_policy) { double("DefaultSSLPolicy", set_custom_certs: nil) } 25 | 26 | before(:all) do 27 | Chef::Config.reset 28 | end 29 | 30 | before(:each) do 31 | response_body = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsdMicrosoft CorporationOS: 0.0.0 SP: 0.0 Stack: 2.0' 32 | http_response_mock = HTTP::Message.new_response(response_body) 33 | allow(http_client_mock).to receive(:post).and_return(http_response_mock) 34 | allow(HTTPClient).to receive(:new).and_return(http_client_mock) 35 | subject.config[:verbosity] = 0 36 | allow(subject.ui).to receive(:ask).and_return("prompted_password") 37 | allow(Chef::HTTP::DefaultSSLPolicy).to receive(:new) 38 | .with(http_client_mock.ssl_config) 39 | .and_return(ssl_policy) 40 | subject.merge_configs 41 | end 42 | 43 | subject { Chef::Knife::WsmanTest.new(["-m", "localhost"]) } 44 | 45 | it "sets the ssl policy" do 46 | expect(ssl_policy).to receive(:set_custom_certs).twice 47 | subject.run 48 | end 49 | 50 | context "when testing the WSMAN endpoint" do 51 | context "and the service responds" do 52 | context "successfully" do 53 | it "writes a message about a successful connection" do 54 | expect(subject.ui).to receive(:msg) 55 | subject.run 56 | end 57 | end 58 | 59 | context "with an invalid body" do 60 | it "warns for a failed connection and exit with a status of 1" do 61 | response_body = "I am invalid" 62 | http_response_mock = HTTP::Message.new_response(response_body) 63 | allow(http_client_mock).to receive(:post).and_return(http_response_mock) 64 | expect(subject.ui).to receive(:warn) 65 | expect(subject.ui).to receive(:error) 66 | expect(subject).to receive(:exit).with(1) 67 | subject.run 68 | end 69 | end 70 | 71 | context "with a non-200 code" do 72 | it "warns for a failed connection and exits with a status of 1" do 73 | http_response_mock = HTTP::Message.new_response("") 74 | http_response_mock.status = 404 75 | allow(http_client_mock).to receive(:post).and_return(http_response_mock) 76 | expect(subject.ui).to receive(:warn) 77 | expect(subject.ui).to receive(:error) 78 | expect(subject).to receive(:exit).with(1) 79 | subject.run 80 | end 81 | end 82 | end 83 | 84 | context "and the service does not respond" do 85 | error_message = "A connection attempt failed because the connected party did not properly respond after a period of time." 86 | 87 | before(:each) do 88 | allow(http_client_mock).to receive(:post).and_raise(Exception.new(error_message)) 89 | end 90 | 91 | it "exits with a status code of 1" do 92 | expect(subject).to receive(:exit).with(1) 93 | subject.run 94 | end 95 | 96 | it "writes a warning message for each node it fails to connect to" do 97 | expect(subject.ui).to receive(:warn) 98 | expect(subject).to receive(:exit).with(1) 99 | subject.run 100 | end 101 | 102 | it "writes an error message if it fails to connect to any nodes" do 103 | expect(subject.ui).to receive(:error) 104 | expect(subject).to receive(:exit).with(1) 105 | subject.run 106 | end 107 | end 108 | end 109 | 110 | context "when not validating ssl cert" do 111 | before(:each) do 112 | expect(subject.ui).to receive(:msg) 113 | subject.config[:winrm_ssl_verify_mode] = :verify_none 114 | subject.config[:winrm_transport] = :ssl 115 | end 116 | 117 | it "sets verify_mode to verify_none" do 118 | subject.run 119 | expect(http_client_mock.ssl_config.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) 120 | end 121 | end 122 | 123 | context "when testing the WSMAN endpoint with verbose output" do 124 | before(:each) do 125 | subject.config[:verbosity] = 1 126 | end 127 | 128 | context "and the service does not respond" do 129 | it "returns an object with an error message" do 130 | error_message = "A connection attempt failed because the connected party did not properly respond after a period of time." 131 | allow(http_client_mock).to receive(:post).and_raise(Exception.new(error_message)) 132 | expect(subject).to receive(:exit).with(1) 133 | expect(subject).to receive(:output) do |output| 134 | expect(output.error_message).to match(/#{error_message}/) 135 | end 136 | subject.run 137 | end 138 | end 139 | 140 | context "with an invalid body" do 141 | it "includes invalid body in error message" do 142 | response_body = "I am invalid" 143 | http_response_mock = HTTP::Message.new_response(response_body) 144 | allow(http_client_mock).to receive(:post).and_return(http_response_mock) 145 | expect(subject).to receive(:exit).with(1) 146 | expect(subject).to receive(:output) do |output| 147 | expect(output.error_message).to match(/#{response_body}/) 148 | end 149 | subject.run 150 | end 151 | end 152 | 153 | context "and the target node is Windows Server 2008 R2" do 154 | before(:each) do 155 | ws2008r2_response_body = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsdMicrosoft CorporationOS: 0.0.0 SP: 0.0 Stack: 2.0' 156 | http_response_mock = HTTP::Message.new_response(ws2008r2_response_body) 157 | allow(http_client_mock).to receive(:post).and_return(http_response_mock) 158 | end 159 | 160 | it "identifies the stack of the product version as 2.0 " do 161 | expect(subject).to receive(:output) do |output| 162 | expect(output.product_version).to eq "OS: 0.0.0 SP: 0.0 Stack: 2.0" 163 | end 164 | subject.run 165 | end 166 | 167 | it "identifies the protocol version as the current DMTF standard" do 168 | expect(subject).to receive(:output) do |output| 169 | expect(output.protocol_version).to eq "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" 170 | end 171 | subject.run 172 | end 173 | 174 | it "identifies the vendor as the Microsoft Corporation" do 175 | expect(subject).to receive(:output) do |output| 176 | expect(output.product_vendor).to eq "Microsoft Corporation" 177 | end 178 | subject.run 179 | end 180 | end 181 | 182 | context "and the target node is Windows Server 2012 R2" do 183 | before(:each) do 184 | ws2012_response_body = 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsdMicrosoft CorporationOS: 0.0.0 SP: 0.0 Stack: 3.0' 185 | http_response_mock = HTTP::Message.new_response(ws2012_response_body) 186 | allow(http_client_mock).to receive(:post).and_return(http_response_mock) 187 | end 188 | 189 | it "identifies the stack of the product version as 3.0 " do 190 | expect(subject).to receive(:output) do |output| 191 | expect(output.product_version).to eq "OS: 0.0.0 SP: 0.0 Stack: 3.0" 192 | end 193 | subject.run 194 | end 195 | 196 | it "identifies the protocol version as the current DMTF standard" do 197 | expect(subject).to receive(:output) do |output| 198 | expect(output.protocol_version).to eq "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" 199 | end 200 | subject.run 201 | end 202 | 203 | it "identifies the vendor as the Microsoft Corporation" do 204 | expect(subject).to receive(:output) do |output| 205 | expect(output.product_vendor).to eq "Microsoft Corporation" 206 | end 207 | subject.run 208 | end 209 | end 210 | end 211 | end 212 | --------------------------------------------------------------------------------