├── .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 | Action | Description | Default |
40 |
41 |
42 |
43 |
44 | add |
45 | Adds an entry for the given host to a `known_hosts` file |
46 | Yes |
47 |
48 |
49 | remove |
50 | Removes entries for a host from a `known_hosts` file |
51 | |
52 |
53 |
54 |
55 | #### Attributes
56 |
57 |
58 |
59 |
60 | Attribute | Description | Default Value |
61 |
62 |
63 |
64 |
65 | host |
66 |
67 | Name attribute: the FQDN for a host to add to a `known_hosts` file
68 | |
69 | nil |
70 |
71 |
72 | port |
73 |
74 | The host's SSH port
75 | |
76 | 22 |
77 |
78 |
79 | hashed |
80 | A Boolean indicating if SSH is configured to use a hashed `known_hosts` file.
81 | |
82 | true |
83 |
84 |
85 | key |
86 | A full line to add to the file, instead of performing a lookup for the host.
87 | |
88 | nil |
89 |
90 |
91 | user |
92 | A 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 | |
95 | nil |
96 |
97 |
98 | path |
99 | A 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 | |
101 | nil |
102 |
103 |
104 |
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 | Action | Description | Default |
122 |
123 |
124 |
125 |
126 | add |
127 | Adds an entry for the given host to a `ssh_config` file |
128 | Yes |
129 |
130 |
131 | remove |
132 | Removes entries for a host from a `ssh_config` file |
133 | |
134 |
135 |
136 |
137 | #### Attributes
138 |
139 |
140 |
141 |
142 | Attribute | Description | Default Value |
143 |
144 |
145 |
146 |
147 | host |
148 |
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 | |
151 | nil |
152 |
153 |
154 | options |
155 | A hash containing the key-values to write for the host in
156 | |
157 | true |
158 |
159 |
160 | user |
161 | A 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 | |
164 | nil |
165 |
166 |
167 | path |
168 | A 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 | |
170 | nil |
171 |
172 |
173 |
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 | Action | Description | Default |
196 |
197 |
198 |
199 |
200 | add |
201 | Adds an entry to the given user's authorized_keys file |
202 | Yes |
203 |
204 |
205 | remove |
206 | Removes an entry from the given user's authorized_keys file |
207 | |
208 | <\tr>
209 |
210 | modify |
211 | Updates an existing entry to the user's authorized_keys file, but only if the indicated `key` is present |
212 | |
213 | <\tr>
214 |
215 |
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 | Attribute | Description | Default Value |
225 |
226 |
227 |
228 |
229 | type |
230 |
231 | A string representing the type of key. Options include `ssh-rsa, ssh-dss, ssh-ed25519` and others
232 | |
233 | ssh-rsa |
234 |
235 |
236 | options |
237 |
238 | A hash containing the key-value pairs for options. Binary options such as `no-port-forwarding` should have a value of `nil`
239 | |
240 | {} |
241 |
242 |
243 | user |
244 |
245 | The user for which this key should be added
246 | |
247 | none - __REQUIRED__ |
248 |
249 |
250 | comment |
251 |
252 | a comment to add to this entry (generally the `useranme@host` is added as a comment, but this is not required)
253 | |
254 | '' |
255 |
256 |
257 | key |
258 |
259 | the actual key
260 | |
261 | none - __REQUIRED__ |
262 |
263 |
264 |
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
--------------------------------------------------------------------------------