├── .bundle └── config ├── .gitignore ├── .kitchen.yml ├── .rubocop.yml ├── .ruby-version ├── .rvmrc ├── Berksfile ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── README.md ├── ROADMAP.md ├── Rakefile ├── attributes └── default.rb ├── libraries ├── matchers.rb ├── ssh_config_helpers.rb ├── ssh_helpers.rb └── testfile ├── metadata.rb ├── providers ├── authorized_keys.rb ├── config.rb └── known_hosts.rb ├── recipes └── default.rb ├── resources ├── authorized_keys.rb ├── config.rb └── known_hosts.rb ├── spec ├── provider_tests │ ├── config_spec.rb │ └── known_hosts_spec.rb └── spec_helper.rb └── test ├── cookbooks └── ssh_test │ ├── Berksfile │ ├── CHANGELOG.md │ ├── README.md │ ├── libraries │ └── test_data.rb │ ├── metadata.rb │ └── recipes │ ├── authorized_keys.rb │ ├── config.rb │ └── known_hosts.rb └── integration ├── authorized_keys └── serverspec │ ├── authorized_keys_spec.rb │ └── spec_helper.rb ├── config └── serverspec │ └── spec_helper.rb └── known_hosts └── serverspec ├── known_hosts_spec.rb ├── spec_helper.rb └── test_data.rb /.bundle/config: -------------------------------------------------------------------------------- 1 | --- {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .kitchen/ 3 | .kitchen.local.yml 4 | Berksfile.lock 5 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | provisioner: 3 | name: chef_zero 4 | client_rb: 5 | node_name: 'test-node-name' 6 | no_lazy_load: true 7 | 8 | driver_plugin: vagrant 9 | driver_config: 10 | require_chef_omnibus: '12.19' #'latest' 11 | customize: 12 | cpus: 2 13 | memory: 2048 14 | cpuexecutioncap: 75 15 | 16 | platforms: 17 | - name: ubuntu-12.04 18 | driver_config: 19 | box: 'ubuntu/precise64' 20 | run_list: 21 | - recipe[apt] 22 | - name: centos-7.2 23 | driver_config: 24 | box: 'centos/7' 25 | 26 | suites: 27 | - name: known_hosts 28 | run_list: 29 | - recipe[ssh_test::known_hosts] 30 | attributes: 31 | ssh_test: 32 | known_hosts: 33 | test_entry: 'dummy5 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCe1poC4rUN8CoW+bWfij2KvY6wYhHdZsxbhyfwUVEclCDcjf4W9Xlso3dogVYXIlqdaeB7SqXmczvsOKl51UTE6Or7G7tiffwZoe4z4J1tRX9HCi+5ZOjUeLtvu6764mpMoTpEwFekER4osXosJw29Nu6cLeM+REpyH/RMZwpb1FUEIOptdupepWh7vlvv4fu0dw1ir0t2lHuW/QCmNh9umSsIxGSOJMnwt1ohk4LKYLykHEc3RRsWZS9rPjbNmAerrdbhF8FF5dyMi+rde5dcIApDmWz3hQwIrZdB+fF5oITVXulTsyhoERQyJf/70oWSECzTuO/jRudlx870bC7b' 34 | - name: config 35 | run_list: 36 | - recipe[ssh_test::config] 37 | - name: authorized_keys 38 | run_list: 39 | - recipe[ssh_test::authorized_keys] 40 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - 'bin/**/*' 4 | - '.kitchen/**/*' 5 | 6 | AlignParameters: 7 | Enabled: false 8 | ClassAndModuleChildren: 9 | Enabled: false 10 | Encoding: 11 | Enabled: false 12 | LineLength: 13 | Max: 120 14 | Severity: refactor 15 | HashSyntax: 16 | EnforcedStyle: hash_rockets 17 | ClassLength: 18 | Enabled: false 19 | MethodLength: 20 | Max: 25 21 | Severity: refactor 22 | SpaceBeforeFirstArg: 23 | Enabled: false 24 | AbcSize: 25 | Enabled: false 26 | Documentation: 27 | Enabled: false 28 | Style/FrozenStringLiteralComment: 29 | Enabled: false 30 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.0 2 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm --create use 1.9.3@chefssh 2 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.getchef.com' 2 | 3 | metadata 4 | 5 | group :test do 6 | cookbook 'ssh_test', :path => './test/cookbooks/ssh_test' 7 | end 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG for ssh 2 | 3 | ## 0.10.24 4 | * merged #74 to fix a typo 5 | 6 | ## 0.10.22 7 | * #71 add chef13 compatibility (does not break chef 12 compatibility) thanks to tecnocratica 8 | * fix new rubocop issues. 9 | 10 | ## 0.10.20 11 | * allow full line comments on authorized_keys 12 | 13 | ## 0.10.18 14 | * Cleanup tests to get them running again 15 | * updates for rubocop errors 16 | * Better handling for custom ssh ports - thanks @stissot 17 | * handle more than one option in authorized-hosts correctly - thanks @chazzly and @rongshen-daqri 18 | * Handle hashed vs non-hashed existing keys correctly - thanks @balous 19 | 20 | ## 0.10.16 21 | * #58 fix deprication warning in default value of provider (thanks to @CloCkWeRX) 22 | * #59 - fix known_hosts diretctory creation (thanks to @atward) 23 | 24 | ## 0.10.14 25 | * #54 fix issues in readme (Thanks to @javierav) 26 | * #57 remove un-needed conditional (thanks to @elser82) 27 | * several updates to get rubocop and foodcritic happy 28 | 29 | ## 0.10.12 30 | * Updated authorized_keys to allow for commas, quotes, and spaces inside the options. 31 | * fixed a bug that was adding a single space to the end of entries. 32 | 33 | ## 0.10.10 34 | * Fixed default key type for authorized keys 35 | * Added some basic validation to ssh keys in authorized_keys provider 36 | 37 | ## 0.10.8 38 | * added matchers for authorized_keys 39 | 40 | ## 0.10.6 41 | * add authorized_keys resource 42 | 43 | ## 0.10.5 44 | * add support for RHEL family 45 | 46 | ## 0.10.4 47 | * fix github #39 where we use the resource name rather than host (The name attribute) in the config LWRP 48 | 49 | ## 0.10.2 50 | * Update the README 51 | * Fix some spec tests 52 | * Fix bug in `config` that did not allow `HostName` directive 53 | 54 | ## 0.10.0 55 | * MAJOR rewrite, but no breaking changes known of. 56 | 57 | ## 0.6.5 58 | 59 | * Add an option for the ssh port number to known_hosts (Scott Arthur) 60 | 61 | ## 0.6.4 62 | 63 | * Use OHAI to determine the user's $HOME (Tom Duckering) 64 | 65 | ## 0.6.3: 66 | 67 | * Fixed libary to make /root instead of /home/root work (Vincent Gijsen) 68 | * Correct default action for config resource (joelwurtz) 69 | * Use the correct user and path for the remove action (roderik) 70 | 71 | ## 0.6.0: 72 | 73 | * Initial release of ssh 74 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :lint do 4 | gem 'foodcritic', '~> 4.0' 5 | gem 'rubocop' 6 | end 7 | 8 | group :unit do 9 | gem 'berkshelf', '~> 3.1' 10 | gem 'chefspec', '~> 4.1' 11 | end 12 | 13 | group 'integration' do 14 | gem 'kitchen-docker', '~> 1.5.0' 15 | gem 'kitchen-sync' 16 | gem 'kitchen-vagrant', '~> 0.11' 17 | gem 'minitest-chef-handler' 18 | gem 'test-kitchen', '~> 1.2' 19 | gem 'vagrant-wrapper' 20 | end 21 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.3.8) 5 | artifactory (2.6.0) 6 | ast (2.3.0) 7 | berkshelf (3.3.0) 8 | addressable (~> 2.3.4) 9 | berkshelf-api-client (~> 1.2) 10 | buff-config (~> 1.0) 11 | buff-extensions (~> 1.0) 12 | buff-shell_out (~> 0.1) 13 | celluloid (~> 0.16.0) 14 | celluloid-io (~> 0.16.1) 15 | cleanroom (~> 1.0) 16 | faraday (~> 0.9.0) 17 | httpclient (~> 2.6.0) 18 | minitar (~> 0.5.4) 19 | octokit (~> 3.0) 20 | retryable (~> 2.0) 21 | ridley (~> 4.0) 22 | solve (~> 1.1) 23 | thor (~> 0.19) 24 | berkshelf-api-client (1.3.1) 25 | faraday (~> 0.9.1) 26 | httpclient (~> 2.6.0) 27 | buff-config (1.0.1) 28 | buff-extensions (~> 1.0) 29 | varia_model (~> 0.4) 30 | buff-extensions (1.0.0) 31 | buff-ignore (1.2.0) 32 | buff-ruby_engine (0.1.0) 33 | buff-shell_out (0.2.0) 34 | buff-ruby_engine (~> 0.1.0) 35 | builder (3.2.3) 36 | celluloid (0.16.0) 37 | timers (~> 4.0.0) 38 | celluloid-io (0.16.2) 39 | celluloid (>= 0.16.0) 40 | nio4r (>= 1.1.0) 41 | chef (12.18.31) 42 | addressable 43 | bundler (>= 1.10) 44 | chef-config (= 12.18.31) 45 | chef-zero (>= 4.8) 46 | diff-lcs (~> 1.2, >= 1.2.4) 47 | erubis (~> 2.7) 48 | ffi-yajl (~> 2.2) 49 | highline (~> 1.6, >= 1.6.9) 50 | iniparse (~> 1.4) 51 | mixlib-archive (>= 0.2.0) 52 | mixlib-authentication (~> 1.4) 53 | mixlib-cli (~> 1.7) 54 | mixlib-log (~> 1.3) 55 | mixlib-shellout (~> 2.0) 56 | net-sftp (~> 2.1, >= 2.1.2) 57 | net-ssh (>= 2.9, < 4.0) 58 | net-ssh-multi (~> 1.1) 59 | ohai (>= 8.6.0.alpha.1, < 9) 60 | plist (~> 3.2) 61 | proxifier (~> 1.0) 62 | rspec-core (~> 3.5) 63 | rspec-expectations (~> 3.5) 64 | rspec-mocks (~> 3.5) 65 | rspec_junit_formatter (~> 0.2.0) 66 | serverspec (~> 2.7) 67 | specinfra (~> 2.10) 68 | syslog-logger (~> 1.6) 69 | uuidtools (~> 2.1.5) 70 | chef-config (12.18.31) 71 | addressable 72 | fuzzyurl 73 | mixlib-config (~> 2.0) 74 | mixlib-shellout (~> 2.0) 75 | chef-zero (5.3.0) 76 | ffi-yajl (~> 2.2) 77 | hashie (>= 2.0, < 4.0) 78 | mixlib-log (~> 1.3) 79 | rack (~> 2.0) 80 | uuidtools (~> 2.1) 81 | chefspec (4.7.0) 82 | chef (>= 11.14) 83 | fauxhai (~> 3.2) 84 | rspec (~> 3.0) 85 | ci_reporter (1.9.3) 86 | builder (>= 2.1.2) 87 | cleanroom (1.0.0) 88 | dep-selector-libgecode (1.3.1) 89 | dep_selector (1.0.4) 90 | dep-selector-libgecode (~> 1.0) 91 | ffi (~> 1.9) 92 | diff-lcs (1.3) 93 | erubis (2.7.0) 94 | faraday (0.9.2) 95 | multipart-post (>= 1.2, < 3) 96 | fauxhai (3.10.0) 97 | net-ssh 98 | ffi (1.9.17) 99 | ffi-yajl (2.3.0) 100 | libyajl2 (~> 1.2) 101 | foodcritic (4.0.0) 102 | erubis 103 | gherkin (~> 2.11) 104 | nokogiri (~> 1.5) 105 | rake 106 | rufus-lru (~> 1.0) 107 | treetop (~> 1.4) 108 | yajl-ruby (~> 1.1) 109 | fuzzyurl (0.9.0) 110 | gherkin (2.12.2) 111 | multi_json (~> 1.3) 112 | hashie (3.5.3) 113 | highline (1.7.8) 114 | hitimes (1.2.4) 115 | httpclient (2.6.0.1) 116 | iniparse (1.4.2) 117 | ipaddress (0.8.3) 118 | json (2.0.3) 119 | kitchen-docker (1.5.0) 120 | test-kitchen (>= 1.0.0) 121 | kitchen-sync (2.1.2) 122 | net-sftp 123 | test-kitchen (>= 1.0.0) 124 | kitchen-vagrant (0.21.1) 125 | test-kitchen (~> 1.4) 126 | libyajl2 (1.2.0) 127 | mini_portile2 (2.1.0) 128 | minitar (0.5.4) 129 | minitest (4.7.5) 130 | minitest-chef-handler (1.1.0) 131 | ci_reporter (< 2.0) 132 | minitest (~> 4.7.3) 133 | mixlib-archive (0.4.1) 134 | mixlib-log 135 | mixlib-authentication (1.4.1) 136 | mixlib-log 137 | mixlib-cli (1.7.0) 138 | mixlib-config (2.2.4) 139 | mixlib-install (2.1.12) 140 | artifactory 141 | mixlib-shellout 142 | mixlib-versioning 143 | thor 144 | mixlib-log (1.7.1) 145 | mixlib-shellout (2.2.7) 146 | mixlib-versioning (1.1.0) 147 | multi_json (1.12.1) 148 | multipart-post (2.0.0) 149 | net-scp (1.2.1) 150 | net-ssh (>= 2.6.5) 151 | net-sftp (2.1.2) 152 | net-ssh (>= 2.6.5) 153 | net-ssh (3.2.0) 154 | net-ssh-gateway (1.3.0) 155 | net-ssh (>= 2.6.5) 156 | net-ssh-multi (1.2.1) 157 | net-ssh (>= 2.6.5) 158 | net-ssh-gateway (>= 1.2.0) 159 | net-telnet (0.1.1) 160 | nio4r (2.0.0) 161 | nokogiri (1.7.0.1) 162 | mini_portile2 (~> 2.1.0) 163 | octokit (3.8.0) 164 | sawyer (~> 0.6.0, >= 0.5.3) 165 | ohai (8.23.0) 166 | chef-config (>= 12.5.0.alpha.1, < 13) 167 | ffi (~> 1.9) 168 | ffi-yajl (~> 2.2) 169 | ipaddress 170 | mixlib-cli 171 | mixlib-config (~> 2.0) 172 | mixlib-log (>= 1.7.1, < 2.0) 173 | mixlib-shellout (~> 2.0) 174 | plist (~> 3.1) 175 | systemu (~> 2.6.4) 176 | wmi-lite (~> 1.0) 177 | parser (2.4.0.0) 178 | ast (~> 2.2) 179 | plist (3.2.0) 180 | polyglot (0.3.5) 181 | powerpack (0.1.1) 182 | proxifier (1.0.3) 183 | rack (2.0.1) 184 | rainbow (2.2.1) 185 | rake (12.0.0) 186 | retryable (2.0.4) 187 | ridley (4.4.2) 188 | addressable 189 | buff-config (~> 1.0) 190 | buff-extensions (~> 1.0) 191 | buff-ignore (~> 1.1) 192 | buff-shell_out (~> 0.1) 193 | celluloid (~> 0.16.0) 194 | celluloid-io (~> 0.16.1) 195 | chef-config 196 | erubis 197 | faraday (~> 0.9.0) 198 | hashie (>= 2.0.2, < 4.0.0) 199 | httpclient (~> 2.6) 200 | json (>= 1.7.7) 201 | mixlib-authentication (>= 1.3.0) 202 | retryable (~> 2.0) 203 | semverse (~> 1.1) 204 | varia_model (~> 0.4.0) 205 | rspec (3.5.0) 206 | rspec-core (~> 3.5.0) 207 | rspec-expectations (~> 3.5.0) 208 | rspec-mocks (~> 3.5.0) 209 | rspec-core (3.5.4) 210 | rspec-support (~> 3.5.0) 211 | rspec-expectations (3.5.0) 212 | diff-lcs (>= 1.2.0, < 2.0) 213 | rspec-support (~> 3.5.0) 214 | rspec-its (1.2.0) 215 | rspec-core (>= 3.0.0) 216 | rspec-expectations (>= 3.0.0) 217 | rspec-mocks (3.5.0) 218 | diff-lcs (>= 1.2.0, < 2.0) 219 | rspec-support (~> 3.5.0) 220 | rspec-support (3.5.0) 221 | rspec_junit_formatter (0.2.3) 222 | builder (< 4) 223 | rspec-core (>= 2, < 4, != 2.12.0) 224 | rubocop (0.47.1) 225 | parser (>= 2.3.3.1, < 3.0) 226 | powerpack (~> 0.1) 227 | rainbow (>= 1.99.1, < 3.0) 228 | ruby-progressbar (~> 1.7) 229 | unicode-display_width (~> 1.0, >= 1.0.1) 230 | ruby-progressbar (1.8.1) 231 | rufus-lru (1.1.0) 232 | safe_yaml (1.0.4) 233 | sawyer (0.6.0) 234 | addressable (~> 2.3.5) 235 | faraday (~> 0.8, < 0.10) 236 | semverse (1.2.1) 237 | serverspec (2.38.0) 238 | multi_json 239 | rspec (~> 3.0) 240 | rspec-its 241 | specinfra (~> 2.53) 242 | sfl (2.3) 243 | solve (1.2.1) 244 | dep_selector (~> 1.0) 245 | semverse (~> 1.1) 246 | specinfra (2.67.1) 247 | net-scp 248 | net-ssh (>= 2.7, < 5.0) 249 | net-telnet 250 | sfl 251 | syslog-logger (1.6.8) 252 | systemu (2.6.5) 253 | test-kitchen (1.15.0) 254 | mixlib-install (>= 1.2, < 3.0) 255 | mixlib-shellout (>= 1.2, < 3.0) 256 | net-scp (~> 1.1) 257 | net-ssh (>= 2.9, < 5.0) 258 | net-ssh-gateway (~> 1.2) 259 | safe_yaml (~> 1.0) 260 | thor (~> 0.18) 261 | thor (0.19.4) 262 | timers (4.0.4) 263 | hitimes 264 | treetop (1.6.8) 265 | polyglot (~> 0.3) 266 | unicode-display_width (1.1.3) 267 | uuidtools (2.1.5) 268 | vagrant-wrapper (2.0.3) 269 | varia_model (0.4.1) 270 | buff-extensions (~> 1.0) 271 | hashie (>= 2.0.2, < 4.0.0) 272 | wmi-lite (1.0.0) 273 | yajl-ruby (1.3.0) 274 | 275 | PLATFORMS 276 | ruby 277 | 278 | DEPENDENCIES 279 | berkshelf (~> 3.1) 280 | chefspec (~> 4.1) 281 | foodcritic (~> 4.0) 282 | kitchen-docker (~> 1.5.0) 283 | kitchen-sync 284 | kitchen-vagrant (~> 0.11) 285 | minitest-chef-handler 286 | rubocop 287 | test-kitchen (~> 1.2) 288 | vagrant-wrapper 289 | 290 | BUNDLED WITH 291 | 1.13.6 292 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chef SSH 2 | 3 | ## Description 4 | 5 | Provides 3 LWRPs to manage system-wide and per-user `ssh_config` and `known_host` files. 6 | 7 | ## Setup 8 | 9 | Include the `ssh` cookbook via Berkshelf or Librarian. 10 | 11 | cookbook "ssh" 12 | 13 | Or add the following line to your cookbook's `metadata.rb`. 14 | 15 | depends "ssh" 16 | 17 | ## Usage 18 | 19 | When using SSH with Chef deployments, it's crucial to not get any prompts for input. Adding entries to `known_hosts` files and better managing your per-connection configuration can help with this. 20 | 21 | An important thing to note is that if you create a user during a chef run, be sure to reload OHAI data so that the new user will be in the node data. For instance: 22 | 23 | ohai "reload_passwd" do 24 | plugin "passwd" 25 | end 26 | 27 | The ssh cookbook bypasses this need somewhat by using ohai classes directly to discover your users' ssh paths. However 28 | some of your cookbooks may not be as generous. 29 | 30 | ## Resources and Providers 31 | 32 | ### known_hosts 33 | 34 | #### Actions 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
ActionDescriptionDefault
addAdds an entry for the given host to a `known_hosts` fileYes
removeRemoves entries for a host from a `known_hosts` file 
54 | 55 | #### Attributes 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 76 | 77 | 78 | 79 | 80 | 82 | 83 | 84 | 85 | 86 | 88 | 89 | 90 | 91 | 92 | 95 | 96 | 97 | 98 | 99 | 101 | 102 | 103 | 104 |
AttributeDescriptionDefault Value
host 67 | Name attribute: the FQDN for a host to add to a `known_hosts` file 68 | nil
port 74 | The host's SSH port 75 | 22
hashedA Boolean indicating if SSH is configured to use a hashed `known_hosts` file. 81 | true
keyA full line to add to the file, instead of performing a lookup for the host. 87 | nil
userA username to add the `known_hosts` entry for. If unspecified, the known_host will be added system-wide. Note: if specified, the user 93 | must already exist. 94 | nil
pathA full path to a known_hosts file. If used with the `user` attribute, this will take precedence over the path to a user's file, but the file will be created (if necessary) as that user. 100 | nil
105 | 106 | #### Example 107 | 108 | ssh_known_hosts "github.com" do 109 | hashed true 110 | user 'webapp' 111 | end 112 | 113 | 114 | ### config 115 | 116 | #### Actions 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 |
ActionDescriptionDefault
addAdds an entry for the given host to a `ssh_config` fileYes
removeRemoves entries for a host from a `ssh_config` file 
136 | 137 | #### Attributes 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 151 | 152 | 153 | 154 | 155 | 157 | 158 | 159 | 160 | 161 | 164 | 165 | 166 | 167 | 168 | 170 | 171 | 172 | 173 |
AttributeDescriptionDefault Value
host 149 | Name attribute: the string to match when connecting to a host. This can be an IP, FQDN (github.com), or contain wildcards (*.prod.corp) 150 | nil
optionsA hash containing the key-values to write for the host in 156 | true
userA username to add the `ssh_config` entry for. If unspecified, the entry will be added system-wide. Note: if specified, the user 162 | must already exist. 163 | nil
pathA full path to a ssh config file. If used with the `user` attribute, this will take precedence over the path to a user's file, but the file will be created (if necessary) as that user. 169 | nil
174 | 175 | #### Example 176 | 177 | ssh_config "github.com" do 178 | options 'User' => 'git', 'IdentityFile' => '/var/apps/github_deploy_key' 179 | user 'webapp' 180 | end 181 | 182 | ### authorized_keys 183 | The authorized_keys LWRP is considered _Beta_ due to the lack of tests for this resource. Use at your own risk, 184 | and feel free to submit a PR for adding more tests. 185 | 186 | Also of important note, typically when SSH keys are generated, the resulting file will have the type, key, and a comment. 187 | The typical comment is just the `username@host`. This is __NOT__ part of the key. When setting your attributes, 188 | please be sure to set only the key in the `key` field. See the example if you are still uncertain. 189 | 190 | #### Actions 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | <\tr> 209 | 210 | 211 | 212 | 213 | <\tr> 214 | 215 |
ActionDescriptionDefault
addAdds an entry to the given user's authorized_keys fileYes
removeRemoves an entry from the given user's authorized_keys file 
modifyUpdates an existing entry to the user's authorized_keys file, but only if the indicated `key` is present 
216 | 217 | __* please note that there is no `name` attribute for this resource. The name you assign is not used in the provider__ 218 | 219 | #### Attributes 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 233 | 234 | 235 | 236 | 237 | 240 | 241 | 242 | 243 | 244 | 247 | 248 | 249 | 250 | 251 | 254 | 255 | 256 | 257 | 258 | 261 | 262 | 263 | 264 |
AttributeDescriptionDefault Value
type 231 | A string representing the type of key. Options include `ssh-rsa, ssh-dss, ssh-ed25519` and others 232 | ssh-rsa
options 238 | A hash containing the key-value pairs for options. Binary options such as `no-port-forwarding` should have a value of `nil` 239 | {}
user 245 | The user for which this key should be added 246 | none - __REQUIRED__
comment 252 | a comment to add to this entry (generally the `useranme@host` is added as a comment, but this is not required) 253 | ''
key 259 | the actual key 260 | none - __REQUIRED__
265 | 266 | #### Example 267 | 268 | ssh_authorized_keys "for remote access" do 269 | options { 'cert-authority' => nil, :command => '/usr/bin/startup' } 270 | user 'admin' 271 | key 'AAAAB3NzaC1yc2EAAAADAQABAAACAQDzB76TOkrDRaevO3I1qzosRXliAuYdjcMejHwwL5v2hRqTrBePlMW6nqz8/JgLTzHn/KxzkrKLb0GlpPDrJ1KByWGYZsfydUfv7n1+5ogoA7UW7dUc4DoQtGPuy4Xe0enr88VfALlT11aWKAw8K/I39zWiPvJNX3Mks0f3/3smjLaQEnDWWWiawp5YgzJmyzsqZFZrrFCUgv7AP1EjZofWUcRvYEEjMhKsK+G2H2VCN7MpH0cJ97E0bKNQjHBrwGyMLQZUOndGakCuOuTLpikOXSpUUz5LwqCiRIj6iUtWevwk+AYLZwxPYQpCxFceVFDhPDaJQ85vweSq+HEg7hRujq9jO7vM9LIgjqg7fwQ2Ql6zO9NjXv2UalzBi0H2AbKT1V/PpNufPgolyb/dK7Jqpqu7Ytggctl2fGyLe8yVaC9gD+/BBeCl82LZI142kdXmf4WYcZgOgcRgGJrbSZjeMzX6zZpiD1AG3T7xyEn2twmC/TqptmQEAG2BBzGum+S6pU0rnOt2UJngRnviK2vptAWtRlSlsopySOXv+VbqUXhRjHRT/+2nq5Q4BWcjsZaaoo1uWh2glATRnGK995A1zJ3gWrBA+IaC6stKzjSG0KPwLjzHfPKbWjDX76D/qdo0qBN5hBiHDRfmiNqpNYS9NHACDZNVPBS5N1d5BUkyKw==' 272 | type 'ssh-rsa' 273 | comment 'gdidy@coolman.com' 274 | end 275 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | v0.10.0 2 | =========== 3 | 4 | * Make everything idempotent 5 | * Chefspec for everything that can 6 | * Kitchen for anything we can't chefspec (hopefully that's nothing) 7 | * use_inline_resources 8 | * support why_run 9 | * Merge any outstanding PR's I'm comfortable with 10 | * Add CONTRIBUTING.md 11 | 12 | v0.10.1 13 | ========== 14 | * add foodcritic support 15 | 16 | v1.0 17 | ========= 18 | * Switch to MWRP using poise 19 | * Rename some actions and attributes 20 | * known_hosts.key becomes known_hosts.entry 21 | THIS WILL BREAK stuff 22 | 23 | Eventually (PRs are VERY welcome) 24 | =========== 25 | * Add a authorized_keys resource 26 | * Get guardfile figured out 27 | * The order of entries in ssh_config matters. So we need to support a priority or something to set it with. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require 'rubocop/rake_task' 3 | require 'foodcritic' 4 | require 'kitchen' 5 | 6 | # lint tests. Rubocop and Foodcritic 7 | namespace :lint do 8 | desc 'Run Ruby lint checks' 9 | RuboCop::RakeTask.new(:ruby) 10 | 11 | desc 'Run Chef lint checks' 12 | FoodCritic::Rake::LintTask.new(:chef) do |t| 13 | t.options = { 14 | :fail_tags => %w[any], 15 | :tags => %w[~FC001] 16 | } 17 | end 18 | end 19 | 20 | desc 'Run all style checks' 21 | task :lint => %w[lint:chef lint:ruby] 22 | 23 | # Rspec and ChefSpec 24 | desc 'Run ChefSpec examples' 25 | RSpec::Core::RakeTask.new(:spec) 26 | 27 | # Integration tests. Kitchen.ci 28 | desc 'Run Test Kitchen' 29 | task :integration, [:mode] do |_, args| 30 | args.with_defaults(:mode => :always) 31 | Kitchen.logger = Kitchen.default_file_logger 32 | Kitchen::Config.new.instances.each do |instance| 33 | instance.test(:always) 34 | end 35 | end 36 | 37 | # Default 38 | task :default => %w[test] 39 | 40 | desc 'Run only the fastest tests (lint and spec tests)' 41 | task :fast => %w[lint spec] 42 | 43 | desc 'Run everything we have' 44 | task :test => %w[fast integration] 45 | -------------------------------------------------------------------------------- /attributes/default.rb: -------------------------------------------------------------------------------- 1 | default['ssh']['known_hosts_path'] = '/etc/ssh/ssh_known_hosts' 2 | default['ssh']['config_path'] = '/etc/ssh/ssh_config' 3 | 4 | default['ssh']['packages'] = case node['platform_family'] 5 | when 'rhel' 6 | ['openssh-server', 'openssh-clients'] 7 | else 8 | ['ssh'] 9 | end 10 | -------------------------------------------------------------------------------- /libraries/matchers.rb: -------------------------------------------------------------------------------- 1 | # cookbook/libraries/matchers.rb 2 | 3 | if defined?(ChefSpec) 4 | ChefSpec.define_matcher :ssh_config 5 | def add_ssh_config(resource_name) 6 | ChefSpec::Matchers::ResourceMatcher.new(:ssh_config, :add, resource_name) 7 | end 8 | 9 | def remove_ssh_config(resource_name) 10 | ChefSpec::Matchers::ResourceMatcher.new(:ssh_config, :remove, resource_name) 11 | end 12 | 13 | ChefSpec.define_matcher :ssh_known_hosts 14 | def add_ssh_known_hosts(resource_name) 15 | ChefSpec::Matchers::ResourceMatcher.new(:ssh_known_hosts, :add, resource_name) 16 | end 17 | 18 | def remove_ssh_known_hosts(resource_name) 19 | ChefSpec::Matchers::ResourceMatcher.new(:ssh_known_hosts, :remove, resource_name) 20 | end 21 | 22 | ChefSpec.define_matcher :ssh_authorized_keys 23 | def add_authorized_keys(resource_name) 24 | ChefSpec::Matchers::ResourceMatcher.new(:ssh_authorized_keys, :add, resource_name) 25 | end 26 | 27 | def remove_ssh_authorized_keys(resource_name) 28 | ChefSpec::Matchers::ResourceMatcher.new(:ssh_authorized_keys, :remove, resource_name) 29 | end 30 | 31 | def update_ssh_authorized_keys(resource_name) 32 | ChefSpec::Matchers::ResourceMatcher.new(:ssh_authorized_keys, :update, resource_name) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /libraries/ssh_config_helpers.rb: -------------------------------------------------------------------------------- 1 | require_relative 'ssh_helpers' 2 | include Chef::SSH::Helpers 3 | 4 | class Chef 5 | module SSH 6 | module ConfigHelpers 7 | def default_or_user_path(username = nil) 8 | username ? "#{user_dir(username)}/.ssh/config" : node['ssh']['config_path'] 9 | end 10 | 11 | def default?(path) 12 | File.expand_path(path).eql? File.expand_path(node['ssh']['config_path']) 13 | end 14 | 15 | def parse_file(path) # rubocop:disable Style/CyclomaticComplexity 16 | entries = {} 17 | return entries unless ::File.exist?(path) 18 | name = nil 19 | IO.foreach(path) do |line| 20 | next if line =~ /^\s*(#|\r?\n|\s*$)/ # skip lines with only comments or whitespace 21 | 22 | check_name = parse_name(line) 23 | next if check_name && (name = check_name) && (entries[name] = {}) 24 | 25 | key, entry = parse_line(line) 26 | next unless entry 27 | entries[name][key] = entry 28 | end 29 | entries 30 | end 31 | 32 | def to_config(existing_entries) 33 | existing_entries.map do |name, options| 34 | body = if options 35 | options.map { |key, value| " #{key} #{value}" }.join("\n") 36 | else 37 | '' 38 | end 39 | ["Host #{name}", body].join("\n") 40 | end.join("\n\n") + "\n" 41 | end 42 | 43 | def parse_name(line) 44 | matchdata = line.match(/^\s*([h|H]ost\s+)(.*$)/) 45 | matchdata ? matchdata.captures[1].strip : false 46 | end 47 | 48 | def parse_line(line) 49 | matchdata = line.match(/^\s*(\w+)(.*$)/) 50 | return matchdata.captures[0], matchdata.captures[1].strip if matchdata 51 | 52 | Chef::Log.error("Line |#{line}| does not parse correctly") 53 | nil 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /libraries/ssh_helpers.rb: -------------------------------------------------------------------------------- 1 | class Chef 2 | module SSH 3 | module Helpers 4 | require 'etc' 5 | def user_dir(username) 6 | pwent = pwent_for(username) 7 | pwent.dir 8 | end 9 | 10 | def user_group(username) 11 | gid = user_gid(username) 12 | return nil unless gid 13 | 14 | Etc.getgrgid(gid).name 15 | end 16 | 17 | def user_gid(username) 18 | username ? pwent_for(username).gid : nil 19 | end 20 | 21 | def pwent_for(uid) 22 | uid.is_a?(Integer) ? Etc.getpwuid(uid) : Etc.getpwnam(uid) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /libraries/testfile: -------------------------------------------------------------------------------- 1 | Host green blue red 2 | bobby orange 3 | greg=blue 4 | fresh =fred,orange 5 | blob = "go get them" 6 | host number2 7 | alright no -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | name 'ssh' 2 | maintainer 'Tejay Cardon' 3 | maintainer_email 'tejay.cardon@gmail.com' 4 | license 'Apache 2.0' 5 | description 'LWRPs for managing SSH known_hosts and config files' 6 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 7 | version '0.10.24' 8 | issues_url 'https://github.com/markolson/chef-ssh/issues' 9 | source_url 'https://github.com/markolson/chef-ssh' 10 | 11 | supports 'ubuntu' 12 | supports 'rhel' 13 | -------------------------------------------------------------------------------- /providers/authorized_keys.rb: -------------------------------------------------------------------------------- 1 | include Chef::SSH::Helpers 2 | 3 | use_inline_resources 4 | 5 | def whyrun_supported? 6 | true 7 | end 8 | 9 | action :add do 10 | validate_options(new_resource.options, "Resource #{new_resource.name}") if new_resource.options 11 | validate_type(new_resource.type, "Resource #{new_resource.name}") 12 | if @current_resource.exists? 13 | action_update 14 | else 15 | @lines << { :options => new_resource.options, 16 | :type => new_resource.type, 17 | :key => new_resource.key, 18 | :comment => new_resource.comment } 19 | update_file 20 | end 21 | end 22 | 23 | action :update do 24 | return unless @current_resource.exists? 25 | current = @lines.find { |line| line[:key] == new_resource.key } 26 | current[:options] = new_resource.options 27 | current[:type] = new_resource.type 28 | current[:comment] = new_resource.comment 29 | update_file 30 | end 31 | 32 | action :remove do 33 | return unless @current_resource.exists? 34 | @lines.reject! { |line| line[:key] == new_resource.key } 35 | update_file 36 | end 37 | 38 | def update_file 39 | directory ::File.dirname(@path) do 40 | action :create 41 | owner new_resource.user 42 | mode 0o0700 43 | end 44 | 45 | file @path do 46 | action :create 47 | mode 0o0600 48 | owner new_resource.user 49 | content format_lines 50 | end 51 | end 52 | 53 | def format_lines 54 | @lines.collect { |line| line.is_a?(String) ? line : construct_line(line) }.join("\n") + "\n" 55 | end 56 | 57 | def construct_line(line) 58 | joined = if line[:options].nil? 59 | '' 60 | else 61 | line[:options].collect do |key, value| 62 | value.nil? || value.empty? ? key.to_s : "#{key}=\"#{value}\"" 63 | end.join(',') 64 | end 65 | joined << ' ' unless joined.empty? 66 | joined << line[:type] << ' ' << line[:key] 67 | line[:comment] && (joined << ' ' << line[:comment]) 68 | joined 69 | end 70 | 71 | def initialize(new_resource, run_context) 72 | super(new_resource, run_context) 73 | 74 | @path = ::File.join(user_dir(new_resource.user), '.ssh', 'authorized_keys') 75 | 76 | load_current_resource 77 | end 78 | 79 | def load_current_resource 80 | @lines = ::File.exist?(@path) ? parse(::IO.readlines(@path)) : [] 81 | 82 | current_line = @lines.find { |line| line.is_a?(Hash) && line[:key] == @new_resource.key } 83 | @current_resource = Chef::Resource.resource_for_node(:ssh_known_hosts, node).new(@new_resource.name) 84 | @current_resource.exists = current_line 85 | end 86 | 87 | protected 88 | 89 | def parse(current) 90 | current.reduce([]) do |memo, row| 91 | if /^#/ =~ row || row.strip.empty? 92 | memo << row 93 | else 94 | line = {} 95 | # split on whitespace that is not inside of quotes 96 | fields = row.split(/(?!\B"[^"]*)\s(?![^"]*"\B)/) 97 | line[:options] = parse_options(fields.shift) unless types.include? fields[0] 98 | validate_type(fields[0], @path) 99 | line[:type] = fields[0] 100 | line[:key] = fields[1] 101 | line[:comment] = fields[2..-1].join(' ') if fields[2] 102 | memo << line 103 | end 104 | end 105 | end 106 | 107 | def parse_options(text) 108 | options = {} 109 | # split on commas that are not inside quotes 110 | split = text.split(/(?!\B"[^"]*),(?![^"]*"\B)/) 111 | split.each do |group| 112 | validate_options(group, @path) 113 | group = group.split('=') 114 | options[group[0]] = group[1].nil? ? nil : group[1].gsub(/\A"|"\Z/, '') 115 | end 116 | options 117 | end 118 | 119 | def types 120 | @types ||= %w[ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 ssh-dss] 121 | end 122 | 123 | def validate_type(type, source) 124 | valid = types 125 | raise "Invalid Type #{type} in #{source}" unless valid.include? type.to_s 126 | end 127 | 128 | def validate_options(option, source) 129 | if option.is_a? Hash 130 | option.each { |o| validate_options o, source } 131 | return 132 | end 133 | option = option.split('=') if option.is_a? String 134 | 135 | if option[1].nil? || option[1].empty? 136 | validate_binary_option(option[0], source) 137 | else 138 | validate_valued_option(option[0], source) 139 | end 140 | end 141 | 142 | def validate_binary_option(option, source) 143 | @binary_options ||= %w[cert-authority no-agent-forwarding no-port-forwarding no-pty no-user-rc no-X11-forwarding] 144 | raise "Invalid Option in #{source}: #{option}" unless @binary_options.include? option.to_s 145 | end 146 | 147 | def validate_valued_option(option, source) 148 | @other_options ||= %w[command environment from permitopen principals tunnel] 149 | raise "Invalid Option in #{source}: #{option}" unless @other_options.include? option.to_s 150 | end 151 | -------------------------------------------------------------------------------- /providers/config.rb: -------------------------------------------------------------------------------- 1 | include Chef::SSH::ConfigHelpers 2 | 3 | use_inline_resources 4 | 5 | def whyrun_supported? 6 | true 7 | end 8 | 9 | action :add do 10 | unless @new_resource.options.eql? @existing_entries[new_resource.host] 11 | @existing_entries[@new_resource.host] = @new_resource.options 12 | 13 | converge_by "Adding #{@new_resource.host} to #{@path} with #{new_resource.options.inspect}" do 14 | create_directory 15 | create_file 16 | end 17 | end 18 | end 19 | 20 | action :add_if_missing do 21 | action_add unless @current_resource.exists? 22 | end 23 | 24 | action :remove do 25 | if @current_resource.exists? 26 | @existing_entries.delete @new_resource.host 27 | 28 | converge_by "Remove #{@new_resource.host} from #{@path}" do 29 | create_file 30 | end 31 | end 32 | end 33 | 34 | def create_directory 35 | d = directory ::File.dirname(@path) 36 | d.owner @user 37 | d.group @group if @group 38 | d.mode default?(@path) ? 0o0755 : 0o0700 39 | d.path ::File.dirname(@path) 40 | d.recursive true 41 | end 42 | 43 | def create_file 44 | f = file @path 45 | f.owner @user if @user 46 | f.group @group if @group 47 | f.mode default?(@path) ? 0o0644 : 0o0600 48 | f.content "# Created by Chef for #{node.name}\n\n#{to_config(@existing_entries)}" 49 | end 50 | 51 | def load_current_resource 52 | @user = new_resource.user || 'root' 53 | @group = new_resource.group || user_group(@user) 54 | @path = new_resource.path || default_or_user_path(new_resource.user) 55 | @existing_entries = parse_file @path 56 | 57 | @current_resource = Chef::Resource.resource_for_node(:ssh_config, node).new(@new_resource.host) 58 | @current_resource.exists = @existing_entries.key? @new_resource.host 59 | end 60 | -------------------------------------------------------------------------------- /providers/known_hosts.rb: -------------------------------------------------------------------------------- 1 | require 'shellwords' 2 | require 'mixlib/shellout' 3 | 4 | include Chef::SSH::Helpers 5 | 6 | use_inline_resources 7 | 8 | def whyrun_supported? 9 | true 10 | end 11 | 12 | action :add do 13 | unless @current_resource.exists? 14 | directory ::File.dirname(new_resource.path) do 15 | action :create 16 | owner new_resource.user if new_resource.user 17 | group new_resource.group if new_resource.group 18 | mode new_resource.user ? 0o0700 : 0o0755 19 | end 20 | 21 | file new_resource.path do 22 | action :create 23 | mode new_resource.user ? 0o0600 : 0o0644 24 | owner new_resource.user if new_resource.user 25 | group new_resource.group if new_resource.group 26 | end 27 | 28 | execute "add known_host entry for #{new_resource.host}" do 29 | command "echo '#{new_resource.key}' >> #{new_resource.path}" 30 | user new_resource.user if new_resource.user 31 | umask new_resource.user ? 0o077 : 0o022 32 | end 33 | end 34 | end 35 | 36 | action :remove do 37 | return unless @current_resource.exists? 38 | execute "remove known_host entry for #{new_resource.host}" do 39 | command "ssh-keygen -R #{Shellwords.escape(new_resource.host)} -f #{new_resource.path}" 40 | user new_resource.user if new_resource.user 41 | umask new_resource.user ? 0o077 : 0o022 42 | end 43 | end 44 | 45 | def initialize(new_resource, run_context) 46 | super(new_resource, run_context) 47 | 48 | new_resource.path default_or_user_path(new_resource.user) unless new_resource.path 49 | if new_resource.host =~ /:/ 50 | host, port = new_resource.host.split(':') 51 | new_resource.host host 52 | new_resource.port port unless new_resource.port 53 | end 54 | 55 | new_resource.port 22 unless new_resource.port 56 | 57 | new_resource.group user_group(new_resource.user) unless new_resource.group 58 | 59 | load_current_resource 60 | load_key_if_needed 61 | end 62 | 63 | def load_key_if_needed 64 | return if @current_resource.exists? 65 | return if new_resource.key 66 | return if new_resource.action.is_a?(Array) ? new_resource.action.include?(:remove) : new_resource.action == :remove 67 | 68 | keyscan = Mixlib::ShellOut.new( 69 | "ssh-keyscan #{new_resource.hashed ? '-H ' : ''} "\ 70 | "-p #{new_resource.port.to_i} #{Shellwords.escape(new_resource.host)}" 71 | ) 72 | keyscan.run_command 73 | keyscan.error! # this will raise an error if the command failed for any reason. 74 | new_resource.key keyscan.stdout.strip 75 | end 76 | 77 | def load_current_resource 78 | matching_host = new_resource.port == 22 ? new_resource.host : "[#{new_resource.host}]:#{new_resource.port}" 79 | 80 | cmd = 81 | if new_resource.key.nil? 82 | "ssh-keygen #{new_resource.hashed ? '-H ' : ''} -F #{Shellwords.escape(matching_host)} "\ 83 | "-f #{new_resource.path} | grep -F 'Host #{matching_host} found'" 84 | else 85 | "grep -F '#{new_resource.key}' '#{new_resource.path}'" 86 | end 87 | 88 | search = Mixlib::ShellOut.new(cmd) 89 | search.run_command 90 | @current_resource = Chef::Resource.resource_for_node(:ssh_known_hosts, node).new(@new_resource.name) 91 | @current_resource.exists = search.status.success? 92 | end 93 | 94 | protected 95 | 96 | def default_or_user_path(username = nil) 97 | username ? "#{user_dir(username)}/.ssh/known_hosts" : node['ssh']['known_hosts_path'] 98 | end 99 | -------------------------------------------------------------------------------- /recipes/default.rb: -------------------------------------------------------------------------------- 1 | # Author: Tejay Cardon 2 | # Cookbook: ssh 3 | # Recipe: default.rb 4 | 5 | node['ssh']['packages'].each do |package_name| 6 | package package_name 7 | end 8 | -------------------------------------------------------------------------------- /resources/authorized_keys.rb: -------------------------------------------------------------------------------- 1 | actions :add, :remove, :update 2 | default_action :add 3 | 4 | attribute :type, 5 | :kind_of => String, 6 | :default => 'ssh-rsa', 7 | :equal_to => [ 8 | 'ssh-rsa', 9 | 'ecdsa-sha2-nistp256', 10 | 'ecdsa-sha2-nistp384', 11 | 'ecdsa-sha2-nistp521', 12 | 'ssh-ed25519', 13 | 'ssh-dss' 14 | ] 15 | attribute :options, :kind_of => Hash, :default => {} 16 | attribute :key, :kind_of => String, :required => true, :regex => [/^\S*$/] 17 | attribute :comment, :kind_of => String 18 | attribute :user, :kind_of => String, :required => true 19 | 20 | attr_accessor :exists 21 | alias exists? exists 22 | -------------------------------------------------------------------------------- /resources/config.rb: -------------------------------------------------------------------------------- 1 | actions :add, :add_if_missing, :remove 2 | default_action :add 3 | 4 | attribute :host, :kind_of => String, :name_attribute => true 5 | attribute :options, :kind_of => Hash 6 | attribute :user, :kind_of => String 7 | attribute :group, :kind_of => String 8 | attribute :path, :kind_of => String 9 | 10 | attr_accessor :exists 11 | alias exists? exists 12 | -------------------------------------------------------------------------------- /resources/known_hosts.rb: -------------------------------------------------------------------------------- 1 | actions :add, :remove # TODO: add replace action (or add if missing??) 2 | default_action :add 3 | 4 | attribute :host, :kind_of => String, :name_attribute => true 5 | attribute :port, :kind_of => Integer, :default => 22 6 | attribute :hashed, :kind_of => [TrueClass, FalseClass], :default => true 7 | attribute :key, :kind_of => String 8 | attribute :user, :kind_of => String 9 | attribute :group, :kind_of => String 10 | attribute :path, :kind_of => String 11 | 12 | attr_accessor :exists 13 | alias exists? exists 14 | -------------------------------------------------------------------------------- /spec/provider_tests/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # rubocop:disable Metrics/BlockLength 4 | describe 'ssh_config resource' do 5 | let(:chef_run) do 6 | runner = ChefSpec::SoloRunner.new(:platform => 'ubuntu', :version => '12.04', :step_into => 'ssh_config') 7 | runner.converge('ssh_test::config') 8 | end 9 | 10 | let(:default_config) { '/etc/ssh/ssh_config' } 11 | let(:vagrant_config) { '/home/vagrant/.ssh/config' } 12 | 13 | let(:test_user) { 'someone' } 14 | let(:test_group) { 'other_group' } 15 | let(:test_config) { '/some/random/path/config' } 16 | 17 | let(:partial_start) do 18 | content = [] 19 | content << 'Host somewhere' 20 | content << 'not indented' 21 | content << 'and_also_with=equal sign' 22 | content << 'Host second with extra patterns' 23 | content << ' Extra_spaces did_not_matter' 24 | content << ' HostName option is correctly matched' 25 | content << ' Multiple_words not a problem' 26 | content << ' We "can handle quotes"' 27 | end 28 | 29 | let(:common_end) do 30 | content = [] 31 | content << '# Created by Chef for fauxhai.local' 32 | content << '' 33 | end 34 | 35 | let(:partial_end) do 36 | content = [] 37 | content << 'Host somewhere' 38 | content << ' not indented' 39 | content << ' and_also_with =equal sign' 40 | content << '' 41 | content << 'Host second with extra patterns' 42 | content << ' Extra_spaces did_not_matter' 43 | content << ' HostName option is correctly matched' 44 | content << ' Multiple_words not a problem' 45 | content << ' We "can handle quotes"' 46 | content << '' 47 | end 48 | 49 | let(:github_end) do 50 | content = [] 51 | content << 'Host github.com' 52 | content << ' User git' 53 | content << ' IdentityFile /tmp/gh' 54 | content << '' 55 | end 56 | 57 | let(:test_end) do 58 | content = [] 59 | content << 'Host test.io' 60 | content << ' User testuser' 61 | content << ' DummyKey I was allowed' 62 | content << '' 63 | end 64 | 65 | let(:github_and_partial_end) do 66 | partial_end + github_end 67 | end 68 | 69 | let(:test_and_partial_end) do 70 | partial_end + test_end 71 | end 72 | 73 | before do 74 | allow(::File).to receive(:'exist?').and_call_original 75 | allow(::File).to receive(:'exist?').with(vagrant_config).and_return(true) 76 | allow(::File).to receive(:'exist?').with(default_config).and_return(true) 77 | allow(::File).to receive(:'exist?').with(test_config).and_return(true) 78 | 79 | allow(IO).to receive(:foreach) 80 | allow(IO).to partial_start.reduce(receive(:foreach).with(vagrant_config), :and_yield) 81 | allow(IO).to partial_start.reduce(receive(:foreach).with(default_config), :and_yield) 82 | allow(IO).to partial_start.reduce(receive(:foreach).with(test_config), :and_yield) 83 | 84 | allow(Etc).to receive(:getgrgid).and_raise(Exception.new('This should not happen')) 85 | allow(Etc).to receive(:getgrgid).with(200).and_return(Struct.new(:name).new('vagrant')) 86 | allow(Etc).to receive(:getgrgid).with(100).and_return(Struct.new(:name).new('someone')) 87 | allow(Etc).to receive(:getgrgid).with(0).and_return(Struct.new(:name).new('root')) 88 | 89 | allow(Etc).to receive(:getpwnam).and_raise(Exception.new('This should not happen')) 90 | allow(Etc).to receive(:getpwnam).with('vagrant').and_return( 91 | Struct.new(:gid, :dir).new(200, '/home/vagrant') 92 | ) 93 | allow(Etc).to receive(:getpwnam).with('someone').and_return( 94 | Struct.new(:gid).new(100) 95 | ) 96 | allow(Etc).to receive(:getpwnam).with('root').and_return( 97 | Struct.new(:gid).new(0) 98 | ) 99 | end 100 | 101 | it 'works when no config file currently exists' do 102 | allow(::File).to receive(:'exist?').with(default_config).and_return(false) 103 | 104 | expect(chef_run).to render_file(default_config).with_content( 105 | (common_end + github_end).join("\n") 106 | ) 107 | end 108 | 109 | it 'maintains existing config entries' do 110 | expect(chef_run).to render_file(default_config).with_content( 111 | (common_end + github_and_partial_end).join("\n") 112 | ) 113 | end 114 | 115 | it 'properly handles configs with "=" signs in them' do 116 | expect(partial_start.join("\n")).to match(/^\s*\w+\s*=\s*\w/) 117 | 118 | expect(chef_run).to render_file(default_config).with_content( 119 | (common_end + github_and_partial_end).join("\n") 120 | ) 121 | end 122 | 123 | it 'properly handles configs with extra whitespace between keywords and arguments' do 124 | expect(partial_start.join("\n")).to match(/^\s*\w+\s\s+\w/) 125 | 126 | expect(chef_run).to render_file(default_config).with_content( 127 | (common_end + github_and_partial_end).join("\n") 128 | ) 129 | end 130 | 131 | it 'properly handles configs that do not indent' do 132 | host_lines_removed = partial_start.reject { |line| line.match(/^[h|H]ost/) } 133 | expect(host_lines_removed.join("\n")).to match(/^\w+\s+\w+/) 134 | 135 | expect(chef_run).to render_file(default_config).with_content( 136 | (common_end + github_and_partial_end).join("\n") 137 | ) 138 | end 139 | 140 | it 'properly handles host declarations with multiple patterns' do 141 | host_lines_only = partial_start.select { |line| line.match(/^[h|H]ost/) } 142 | expect(host_lines_only.join("\n")).to match(/^\w+\s+\w+\s+\w+/) 143 | 144 | expect(chef_run).to render_file(default_config).with_content( 145 | (common_end + github_and_partial_end).join("\n") 146 | ) 147 | end 148 | 149 | it 'properly handles config elements with quotes' do 150 | expect(partial_start.join("\n")).to match(/^\s*\w+\s+".+"/) 151 | 152 | expect(chef_run).to render_file(default_config).with_content( 153 | (common_end + github_and_partial_end).join("\n") 154 | ) 155 | end 156 | 157 | it 'properly handles config elements with multiple words' do 158 | host_lines_removed = partial_start.reject { |line| line.match(/^[h|H]ost/) } 159 | expect(host_lines_removed.join("\n")).to match(/^\s*\w+\s+\w+\s+\w+/) 160 | 161 | expect(chef_run).to render_file(default_config).with_content( 162 | (common_end + github_and_partial_end).join("\n") 163 | ) 164 | end 165 | 166 | it 'properly handles config without empty lines between configs' do 167 | found_one = false 168 | partial_start.each_with_index do |line, index| 169 | found_one = true if line.match(/^\s*[H|h]ost/) && !partial_start[index].empty? 170 | break if found_one 171 | end 172 | end 173 | 174 | it 'can create user ssh configs' do 175 | expect(chef_run).to create_file(vagrant_config).with( 176 | :owner => 'vagrant', 177 | :group => 'vagrant', 178 | :mode => 0o0600 179 | ).with_content( 180 | (common_end + github_and_partial_end).join("\n") 181 | ) 182 | end 183 | 184 | it 'can create the global ssh config' do 185 | expect(chef_run).to create_file(default_config).with( 186 | :owner => 'root', 187 | :group => 'root', 188 | :mode => 0o0644 189 | ).with_content( 190 | (common_end + github_and_partial_end).join("\n") 191 | ) 192 | end 193 | 194 | it 'creates the /etc/ssh directory if it is missing' do 195 | expect(chef_run).to create_directory(::File.dirname(default_config)).with( 196 | :owner => 'root', 197 | :group => 'root', 198 | :mode => 0o0755 199 | ) 200 | end 201 | 202 | it "creates vagrant's ~/.ssh/config file" do 203 | expect(chef_run).to create_directory(::File.dirname(vagrant_config)).with( 204 | :owner => 'vagrant', 205 | :group => 'vagrant', 206 | :mode => 0o0700 207 | ) 208 | end 209 | 210 | context 'when non-default attributes are used' do 211 | it 'can handle a custom path' do 212 | expect(chef_run).to render_file(test_config).with_content( 213 | (common_end + test_and_partial_end).join("\n") 214 | ) 215 | end 216 | 217 | it 'can handle a custom owner and group for the config file' do 218 | expect(chef_run).to create_file(test_config).with( 219 | :owner => test_user, 220 | :group => test_group, 221 | :mode => 0o0600 222 | ) 223 | end 224 | end 225 | 226 | it 'can handle files with comments in them' do 227 | content = [] 228 | content << '# this is a comment line' 229 | content += partial_start 230 | allow(IO).to content.reduce(receive(:foreach).with(default_config), :and_yield) 231 | 232 | expect(chef_run).to render_file(default_config).with_content( 233 | (common_end + github_and_partial_end).join("\n") 234 | ) 235 | end 236 | 237 | it 'does not duplicate entries' do 238 | allow(IO).to github_and_partial_end.reduce(receive(:foreach).with(vagrant_config), :and_yield) 239 | 240 | expect(chef_run).to render_file(default_config).with_content( 241 | (common_end + github_and_partial_end).join("\n") 242 | ) 243 | end 244 | end 245 | -------------------------------------------------------------------------------- /spec/provider_tests/known_hosts_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'ssh_config resource' do 4 | let(:chef_run) do 5 | runner = ChefSpec::SoloRunner.new(:step_into => :ssh_config) 6 | runner.converge('ssh_test::known_hosts') 7 | end 8 | 9 | let(:default_known_hosts) { '/etc/ssh/ssh_known_hosts' } 10 | let(:vagrant_known_hosts) { '/home/vagrant/.ssh/known_hosts' } 11 | 12 | let(:test_user) { 'someone' } 13 | let(:test_group) { 'other_group' } 14 | let(:test_known_hosts) { '/some/random/path/config' } 15 | 16 | before do 17 | allow(Etc).to receive(:getpwnam) 18 | allow(Etc).to receive(:getpwnam).with(test_user).and_return( 19 | Struct.new(:gid, :dir).new(200, "/home/#{test_user}") 20 | ) 21 | allow(Etc).to receive(:getpwnam).with('root').and_return( 22 | Struct.new(:gid).new(0) 23 | ) 24 | end 25 | 26 | pending 'I can not think of any spec tests that make sense' 27 | end 28 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | require 'rspec/mocks/message_expectation' 4 | 5 | RSpec.configure do |config| 6 | config.color = true 7 | 8 | config.log_level = :error 9 | end 10 | 11 | ChefSpec::Coverage.start! 12 | -------------------------------------------------------------------------------- /test/cookbooks/ssh_test/Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.getchef.com' 2 | 3 | metadata 4 | cookbook 'ssh' 5 | -------------------------------------------------------------------------------- /test/cookbooks/ssh_test/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ssh_test CHANGELOG 2 | ================== 3 | 4 | This file is used to list changes made in each version of the ssh_test cookbook. 5 | 6 | 0.1.0 7 | ----- 8 | - Initial release of ssh_test 9 | 10 | 0.2.0 11 | ----- 12 | - Broke default.rb into config.rb and known_hosts.rb and cleaned it up to support chefspec tests. 13 | 14 | 15 | 16 | - - - 17 | Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown. 18 | 19 | The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown. 20 | -------------------------------------------------------------------------------- /test/cookbooks/ssh_test/README.md: -------------------------------------------------------------------------------- 1 | Recipes 2 | ========= 3 | default.rb 4 | ------------- 5 | does nothing 6 | 7 | config.rb 8 | ------------ 9 | exercises the `ssh_config` lwrp in several ways. 10 | 11 | known_hosts.rb 12 | ------------ 13 | exercises the `ssh_known_hosts` lwrp in several ways. -------------------------------------------------------------------------------- /test/cookbooks/ssh_test/libraries/test_data.rb: -------------------------------------------------------------------------------- 1 | module TestData 2 | def self.github_key 3 | 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9'\ 4 | 'IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsy'\ 5 | 'COV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU'\ 6 | '84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqq'\ 7 | 'UUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy2'\ 8 | '8G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' 9 | end 10 | 11 | def self.bitbucket_altssh_key 12 | '[altssh.bitbucket.org]:443 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubi'\ 13 | 'N81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYK'\ 14 | 'iEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusM'\ 15 | 'EASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9'\ 16 | 'IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjO'\ 17 | 'bPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBo'\ 18 | 'GqzHM9yXw==' 19 | end 20 | 21 | def self.dummy1_key 22 | 'dummy1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSw'\ 23 | 'BK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV'\ 24 | '0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84'\ 25 | 'KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqU'\ 26 | 'UmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy2'\ 27 | '8G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' 28 | end 29 | 30 | def self.dummy2_key 31 | 'dummy2 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSw'\ 32 | 'BK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV'\ 33 | '0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84'\ 34 | 'KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqU'\ 35 | 'UmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy2'\ 36 | '8G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' 37 | end 38 | 39 | def self.dummy3_key 40 | 'dummy3 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSw'\ 41 | 'BK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV'\ 42 | '0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84'\ 43 | 'KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqU'\ 44 | 'UmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy2'\ 45 | '8G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' 46 | end 47 | 48 | def self.dummy4_key 49 | 'dummy4 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwDBTE5H+DpOWUv3CPtOo'\ 50 | 'tliP6GNG//nVBqQkGYYdDp9eOeZUO8cll78UTXZ96AigoRNIHp/NvZNY+6IOblNGEA'\ 51 | 'ZxxF4tW9hy9X57IQvRaRTQaAUoT9QWGHtwejsYQozL6mDtx9/TQpfWrUhBWWEUCJyr'\ 52 | 'bLcnS6jtstieKlyEDM4gJmNyKy+Af7kigfWRF5EzBUYAJbQHSjKG8u8NQnvKFSup0o'\ 53 | 'xTf28m5Etzgf9B+4qUl1oMGsKrq9RQ/W1imSJra1nxE0vWr8+FkIONTxPdhRGOUMSA'\ 54 | 'T1r6bfIWpdTFF/uc87EA3LY3LkuDbQHHW4kEv1/b0hHYwWcBLbwWJ8mi3' 55 | end 56 | 57 | def self.dummy6_key 58 | '[dummy6]:234 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwDBTE5H+DpOWUv3CPtOo'\ 59 | 'tliP6GNG//nVBqQkGYYdDp9eOeZUO8cll78UTXZ96AigoRNIHp/NvZNY+6IOblNGEA'\ 60 | 'ZxxF4tW9hy9X57IQvRaRTQaAUoT9QWGHtwejsYQozL6mDtx9/TQpfWrUhBWWEUCJyr'\ 61 | 'bLcnS6jtstieKlyEDM4gJmNyKy+Af7kigfWRF5EzBUYAJbQHSjKG8u8NQnvKFSup0o'\ 62 | 'xTf28m5Etzgf9B+4qUl1oMGsKrq9RQ/W1imSJra1nxE0vWr8+FkIONTxPdhRGOUMSA'\ 63 | 'T1r6bfIWpdTFF/uc87EA3LY3LkuDbQHHW4kEv1/b0hHYwWcBLbwWJ8mi3' 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/cookbooks/ssh_test/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'ssh_test' 2 | maintainer 'Tejay Cardon' 3 | maintainer_email 'tejay.cardon@gmail.com' 4 | license 'Apache 2.0' 5 | description 'Runs some tests for Chef-SSH' 6 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 7 | version '0.2.0' 8 | 9 | depends 'ssh' 10 | depends 'apt' 11 | -------------------------------------------------------------------------------- /test/cookbooks/ssh_test/recipes/authorized_keys.rb: -------------------------------------------------------------------------------- 1 | user 'test-user' do 2 | manage_home true 3 | manage_home true 4 | home '/home/test-user' 5 | action %i[create manage] 6 | end 7 | 8 | directory '/home/test-user/.ssh' do 9 | mode 0o0700 10 | end 11 | 12 | file '/home/test-user/.ssh/authorized_keys' do 13 | content "# this is a comment\n" + 'ssh-rsa iWasHereAAAAB2EAAAADAQABAAACAQCeCRfSzGWGNsisAZpuFIS0GmHJfgms3g8okwL9h9AvoQPwgyhyri/Wlcz3eyZMvuR4/vwh9FgWpRwLxot7QSGry58GYR9tHkDT9o3m0Hlx28E+K2gbNK5SyFROx5lSfOZkCSyPjBEBmTAadpVYZBJj789oeAT3dDvsxMAqokCIjV5Ey9xBIWKapbsDiTdOHmtDhlrFZfBc75I6tTnW9WGVG6gCQtzyC/tJ2DmWJhtEz9UjxhAOUzazHM2CJ2IlF3SHm+nz7xjTWmGVRzpiellmN+2Stmianea9ga8L//9v06gDKqp2lNSsi2SJujAsEiKAGtQu6Aa4hdxRFt87m6WSN9lusAazZvnX5s93lAmUAG+wWPnAsujkRSDwv2Ju+GdQFW3ncML7aXFOhIMViG6B98X2h9f3W6XdwQseh10QfvFZ3fAmcAvWvlEM0pGXdfKeFY0LfD7UFxTvzEfqPKnbV6SKlAIMAQ3CX+Q1sZ4nfqopZVJwHDHSL/KQeVKePdyFbZcFVE4L/zruS/fLDqiDMq9yZqMu3WkP5bp4crzguaVwHmrTG4k1XOH5jkMrUj7javMLQHWu56bj0heynhXw7gzXnC/DSgY58/1BPEy7ejsGr0RX2LBRulh84UkV0cjLs8MZyBrhS4dYwyBmtcYlh+OVVVwFimg4ayR7UlkVMw==' # rubocop:disable all' 14 | end 15 | 16 | ssh_authorized_keys 'first' do 17 | user 'test-user' 18 | type 'ssh-rsa' 19 | options 'no-agent-forwarding' => nil, 'no-pty' => nil, :command => 'ls /root', :from => '10.10.1.2,10.4.2.1' 20 | comment 'I left a comment' 21 | key 'AAAAB3NzaC1yc2EAAAADAQABAAACAQCeCRfSzGWGNsisAZpuFIS0GmHJfgms3g8okwL9h9AvoQPwgyhyri/Wlcz3eyZMvuR4/vwh9FgWpRwLxot7QSGry58GYR9tHkDT9o3m0Hlx28E+K2gbNK5SyFROx5lSfOZkCSyPjBEBmTAadpVYZBJj789oeAT3dDvsxMAqokCIjV5Ey9xBIWKapbsDiTdOHmtDhlrFZfBc75I6tTnW9WGVG6gCQtzyC/tJ2DmWJhtEz9UjxhAOUzazHM2CJ2IlF3SHm+nz7xjTWmGVRzpiellmN+2StmibuFkoZP8L//9v06gDKqp2lNSsi2SJujAsEiKAGtQu6Aa4hdxRFt87m6WSN9lusAazZvnX5s93lAmUAG+wWPnAsujkRSDwv2Ju+GdQFW3ncML7aXFOhIMViG6B98X2h9f3W6XdwQseh10QfvFZ3fAmcAvWvlEM0pGXdfKeFY0LfD7UFxTvzEfqPKnbV6SKlAIMAQ3CX+Q1sZ4nfqopZVJwHDHSL/KQeVKePdyFbZcFVE4L/zruS/fLDqiDMq9yZqMu3WkP5bp4crzguaVwHmrTG4k1XOH5jkMrUj7javMLQHWu56bj0heynhXw7gzXnC/DSgY58/1BPEy7ejsGr0RX2LBRulh84UkV0cjLs8MZyBrhS4dYwyBmtcYlh+OVVVwFimg4ayR7UlkVMw==' # rubocop:disable all 22 | end 23 | 24 | ssh_authorized_keys 'without comment' do 25 | user 'test-user' 26 | type 'ssh-rsa' 27 | key 'AAAAB3NzaC1yc2EAAAADAQABAAACAQC5ulpHLJfGxi/sMjqOSWVKbW4yeNzWF2VAZYM/QYqH/9LUuEwBAgxFB96dxDn+AY9UxM2lmiYajsHHF6qqnRPuK2SVHKZ8SSR/yEWL6AtkeX5z0QgzCqXLAgMUbzRy6TRb/SM8tqQ+ybVGiVaSK+UGT5saFY3WLmhq2wJva5TzRhxtL+vnQMNUDaFylTZDqw9Y0GFHkqEvcU2L0bcLlhEr0NVUFwz0M2yW0mx+Y8wwwUxxFhbTsjxV2c6Fa/bmqSN1I6z5K0hYQRyPlNpjEKlKT57TTHrlSCsf+Id2DOC1EbEVcCtIT6tIlahys9qayYM/36nbD0ru0NgDY2NliWlTk+F7L9gBncPdCib9euz3JqyftnNGT9OzpK8A9U4nKDVdaQKVN6Eat4ViEmwBnO8CeaT2XDKVLOJcRnaZu6pYFvbZcyBC0DPThksvl1pTlnBr7aypW/15lkXY1vNPa6mvyfFjO5SHDCBvMRSaCB4NqGreaVaomvnvMv+wwn+PCNmvTSv2PfZzjWHbp5wip7x9bNmF8s1w2knOR3appjEDnJm6YR4KuXMRxr4zsAxVvC5uOhs4JVh91Ujtec/aVKXBQuggT8tX00M6rSamMbvNkmlA6WIVrwmYvCRIo8GqKKxU8lJJcdt/y/qa4jIdd5Z6mWQmhy8Jw8La7J2GWvX3Ow==' # rubocop:disable all 28 | end 29 | 30 | ssh_authorized_keys 'type defaults' do 31 | user 'test-user' 32 | key 'AAAAB3NzaC1yc2EAAAADAQABAAACAQDzB76TOkrDRaevO3I1qzosRXliAuYdjcMejHwwL5v2hRqTrBePlMW6nqz8/JgLTzHn/KxzkrKLb0GlpPDrJ1KByWGYZsfydUfv7n1+5ogoA7UW7dUc4DoQtGPuy4Xe0enr88VfALlT11aWKAw8K/I39zWiPvJNX3Mks0f3/3smjLaQEnDWWWiawp5YgzJmyzsqZFZrrFCUgv7AP1EjZofWUcRvYEEjMhKsK+G2H2VCN7MpH0cJ97E0bKNQjHBrwGyMLQZUOndGakCuOuTLpikOXSpUUz5LwqCiRIj6iUtWevwk+AYLZwxPYQpCxFceVFDhPDaJQ85vweSq+HEg7hRujq9jO7vM9LIgjqg7fwQ2Ql6zO9NjXv2UalzBi0H2AbKT1V/PpNufPgolyb/dK7Jqpqu7Ytggctl2fGyLe8yVaC9gD+/BBeCl82LZI142kdXmf4WYcZgOgcRgGJrbSZjeMzX6zZpiD1AG3T7xyEn2twmC/TqptmQEAG2BBzGum+S6pU0rnOt2UJngRnviK2vptAWtRlSlsopySOXv+VbqUXhRjHRT/+2nq5Q4BWcjsZaaoo1uWh2glATRnGK995A1zJ3gWrBA+IaC6stKzjSG0KPwLjzHfPKbWjDX76D/qdo0qBN5hBiHDRfmiNqpNYS9NHACDZNVPBS5N1d5BUkyKw==' # rubocop:disable all 33 | end 34 | -------------------------------------------------------------------------------- /test/cookbooks/ssh_test/recipes/config.rb: -------------------------------------------------------------------------------- 1 | # Author: Tejay Cardon 2 | 3 | include_recipe 'ssh' 4 | 5 | ssh_config 'github.com' do 6 | options 'User' => 'git', 'IdentityFile' => '/tmp/gh' 7 | end 8 | 9 | ssh_config 'github.com' do 10 | options 'User' => 'git', 'IdentityFile' => '/tmp/gh' 11 | user 'vagrant' 12 | end 13 | 14 | group 'other_group' 15 | user 'someone' 16 | 17 | ssh_config 'something else' do 18 | host 'test.io' 19 | options 'User' => 'testuser', 'DummyKey' => 'I was allowed' 20 | user 'someone' 21 | group 'other_group' 22 | path '/some/random/path/config' 23 | end 24 | -------------------------------------------------------------------------------- /test/cookbooks/ssh_test/recipes/known_hosts.rb: -------------------------------------------------------------------------------- 1 | # Author: Tejay Cardon 2 | 3 | include_recipe 'ssh' 4 | 5 | file '/etc/ssh/ssh_known_hosts' do 6 | content "# this is a comment\n#{::TestData.dummy1_key}\n#{::TestData.dummy2_key}\n"\ 7 | "#{::TestData.dummy6_key}\n" 8 | action :create 9 | end 10 | 11 | file '/home/vagrant/.ssh/known_hosts' do 12 | content "#{::TestData.dummy3_key}\n# this is a comment\n#{::TestData.dummy4_key}\n" 13 | action :create 14 | end 15 | 16 | ssh_known_hosts 'github.com' do 17 | hashed false 18 | end 19 | 20 | ssh_known_hosts 'github.com' do 21 | hashed true 22 | user 'vagrant' 23 | port 22 24 | end 25 | 26 | # Simulate multiple converges to test idempotence 27 | (1..3).step do |n| 28 | ssh_known_hosts "altssh.bitbucket.org converge #{n}" do 29 | host 'altssh.bitbucket.org' 30 | hashed true 31 | port 443 32 | user 'root' 33 | key ::TestData.bitbucket_altssh_key 34 | end 35 | end 36 | 37 | ssh_known_hosts 'some entry' do 38 | host 'test_host' 39 | user 'vagrant' 40 | key node['ssh_test']['known_hosts']['test_entry'] 41 | end 42 | 43 | ssh_known_hosts 'dummy5' do 44 | key node['ssh_test']['known_hosts']['test_entry'] 45 | end 46 | 47 | ssh_known_hosts 'dummy2' do 48 | action :remove 49 | end 50 | 51 | ssh_known_hosts 'dummy4' do 52 | user 'vagrant' 53 | action :remove 54 | end 55 | 56 | ssh_known_hosts 'dummy6' do 57 | host 'dummy6' 58 | port 234 59 | user 'vagrant' 60 | key ::TestData.dummy6_key 61 | end 62 | -------------------------------------------------------------------------------- /test/integration/authorized_keys/serverspec/authorized_keys_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe user('test-user') do 4 | it { should exist } 5 | # rubocop:disable all 6 | it { should have_authorized_key 'no-agent-forwarding,no-pty,command="ls /root",from="10.10.1.2,10.4.2.1" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCeCRfSzGWGNsisAZpuFIS0GmHJfgms3g8okwL9h9AvoQPwgyhyri/Wlcz3eyZMvuR4/vwh9FgWpRwLxot7QSGry58GYR9tHkDT9o3m0Hlx28E+K2gbNK5SyFROx5lSfOZkCSyPjBEBmTAadpVYZBJj789oeAT3dDvsxMAqokCIjV5Ey9xBIWKapbsDiTdOHmtDhlrFZfBc75I6tTnW9WGVG6gCQtzyC/tJ2DmWJhtEz9UjxhAOUzazHM2CJ2IlF3SHm+nz7xjTWmGVRzpiellmN+2StmibuFkoZP8L//9v06gDKqp2lNSsi2SJujAsEiKAGtQu6Aa4hdxRFt87m6WSN9lusAazZvnX5s93lAmUAG+wWPnAsujkRSDwv2Ju+GdQFW3ncML7aXFOhIMViG6B98X2h9f3W6XdwQseh10QfvFZ3fAmcAvWvlEM0pGXdfKeFY0LfD7UFxTvzEfqPKnbV6SKlAIMAQ3CX+Q1sZ4nfqopZVJwHDHSL/KQeVKePdyFbZcFVE4L/zruS/fLDqiDMq9yZqMu3WkP5bp4crzguaVwHmrTG4k1XOH5jkMrUj7javMLQHWu56bj0heynhXw7gzXnC/DSgY58/1BPEy7ejsGr0RX2LBRulh84UkV0cjLs8MZyBrhS4dYwyBmtcYlh+OVVVwFimg4ayR7UlkVMw== I left a comment' } 7 | it { should have_authorized_key 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDzB76TOkrDRaevO3I1qzosRXliAuYdjcMejHwwL5v2hRqTrBePlMW6nqz8/JgLTzHn/KxzkrKLb0GlpPDrJ1KByWGYZsfydUfv7n1+5ogoA7UW7dUc4DoQtGPuy4Xe0enr88VfALlT11aWKAw8K/I39zWiPvJNX3Mks0f3/3smjLaQEnDWWWiawp5YgzJmyzsqZFZrrFCUgv7AP1EjZofWUcRvYEEjMhKsK+G2H2VCN7MpH0cJ97E0bKNQjHBrwGyMLQZUOndGakCuOuTLpikOXSpUUz5LwqCiRIj6iUtWevwk+AYLZwxPYQpCxFceVFDhPDaJQ85vweSq+HEg7hRujq9jO7vM9LIgjqg7fwQ2Ql6zO9NjXv2UalzBi0H2AbKT1V/PpNufPgolyb/dK7Jqpqu7Ytggctl2fGyLe8yVaC9gD+/BBeCl82LZI142kdXmf4WYcZgOgcRgGJrbSZjeMzX6zZpiD1AG3T7xyEn2twmC/TqptmQEAG2BBzGum+S6pU0rnOt2UJngRnviK2vptAWtRlSlsopySOXv+VbqUXhRjHRT/+2nq5Q4BWcjsZaaoo1uWh2glATRnGK995A1zJ3gWrBA+IaC6stKzjSG0KPwLjzHfPKbWjDX76D/qdo0qBN5hBiHDRfmiNqpNYS9NHACDZNVPBS5N1d5BUkyKw==' } 8 | it { should have_authorized_key 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC5ulpHLJfGxi/sMjqOSWVKbW4yeNzWF2VAZYM/QYqH/9LUuEwBAgxFB96dxDn+AY9UxM2lmiYajsHHF6qqnRPuK2SVHKZ8SSR/yEWL6AtkeX5z0QgzCqXLAgMUbzRy6TRb/SM8tqQ+ybVGiVaSK+UGT5saFY3WLmhq2wJva5TzRhxtL+vnQMNUDaFylTZDqw9Y0GFHkqEvcU2L0bcLlhEr0NVUFwz0M2yW0mx+Y8wwwUxxFhbTsjxV2c6Fa/bmqSN1I6z5K0hYQRyPlNpjEKlKT57TTHrlSCsf+Id2DOC1EbEVcCtIT6tIlahys9qayYM/36nbD0ru0NgDY2NliWlTk+F7L9gBncPdCib9euz3JqyftnNGT9OzpK8A9U4nKDVdaQKVN6Eat4ViEmwBnO8CeaT2XDKVLOJcRnaZu6pYFvbZcyBC0DPThksvl1pTlnBr7aypW/15lkXY1vNPa6mvyfFjO5SHDCBvMRSaCB4NqGreaVaomvnvMv+wwn+PCNmvTSv2PfZzjWHbp5wip7x9bNmF8s1w2knOR3appjEDnJm6YR4KuXMRxr4zsAxVvC5uOhs4JVh91Ujtec/aVKXBQuggT8tX00M6rSamMbvNkmlA6WIVrwmYvCRIo8GqKKxU8lJJcdt/y/qa4jIdd5Z6mWQmhy8Jw8La7J2GWvX3Ow==' } 9 | # rubocop:enable all 10 | end 11 | -------------------------------------------------------------------------------- /test/integration/authorized_keys/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | 3 | set :backend, :exec 4 | -------------------------------------------------------------------------------- /test/integration/config/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | 3 | set :backend, :exec 4 | -------------------------------------------------------------------------------- /test/integration/known_hosts/serverspec/known_hosts_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative 'test_data' 3 | 4 | describe file('/etc/ssh/ssh_known_hosts') do 5 | it { should be_file } 6 | it { should be_readable } 7 | its(:content) { should include(TestData.github_key) } 8 | its(:content) { should include(TestData.dummy1_key) } 9 | its(:content) { should_not match(/dummy2/) } 10 | its(:content) { is_expected.to contain('# this is a comment') } 11 | end 12 | 13 | describe file('/home/vagrant/.ssh/known_hosts') do 14 | it { should be_file } 15 | it { should be_readable } 16 | its(:content) { should include(TestData.dummy3_key) } 17 | its(:content) { should_not match(/dummy4/) } 18 | its(:content) { is_expected.to contain('# this is a comment') } 19 | end 20 | 21 | describe command('ssh-keygen -F github.com -f /home/vagrant/.ssh/known_hosts') do 22 | its(:stdout) { should include(TestData.github_key.split(' ')[2]) } 23 | its(:stdout) { should match(/found/) } 24 | end 25 | 26 | describe command('ssh-keygen -F [altssh.bitbucket.org]:443 -f /root/.ssh/known_hosts | grep found | wc -l') do 27 | its(:stdout) { should match(/1/) } 28 | end 29 | 30 | describe file('/home/vagrant/.ssh/known_hosts') do 31 | it { should be_file } 32 | its(:content) { should match(/\[dummy6\]\:234 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwDBTE5H\+DpOWUv3CPtOo/) } 33 | its(:content) { should_not match(/\[dummy6\]\:234[\s\S]*\[dummy6\]\:234/) } 34 | end 35 | -------------------------------------------------------------------------------- /test/integration/known_hosts/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | 3 | set :backend, :exec 4 | -------------------------------------------------------------------------------- /test/integration/known_hosts/serverspec/test_data.rb: -------------------------------------------------------------------------------- 1 | ../../../cookbooks/ssh_test/libraries/test_data.rb --------------------------------------------------------------------------------