1# This file is part of cloud-init. See LICENSE file for license information.
2
3import os
4
5from collections import namedtuple
6from functools import partial
7from unittest.mock import patch
8
9from cloudinit import ssh_util
10from cloudinit.tests import helpers as test_helpers
11from cloudinit import util
12
13# https://stackoverflow.com/questions/11351032/
14FakePwEnt = namedtuple('FakePwEnt', [
15    'pw_name',
16    'pw_passwd',
17    'pw_uid',
18    'pw_gid',
19    'pw_gecos',
20    'pw_dir',
21    'pw_shell',
22])
23FakePwEnt.__new__.__defaults__ = tuple(
24    "UNSET_%s" % n for n in FakePwEnt._fields)
25
26
27def mock_get_owner(updated_permissions, value):
28    try:
29        return updated_permissions[value][0]
30    except ValueError:
31        return util.get_owner(value)
32
33
34def mock_get_group(updated_permissions, value):
35    try:
36        return updated_permissions[value][1]
37    except ValueError:
38        return util.get_group(value)
39
40
41def mock_get_user_groups(username):
42    return username
43
44
45def mock_get_permissions(updated_permissions, value):
46    try:
47        return updated_permissions[value][2]
48    except ValueError:
49        return util.get_permissions(value)
50
51
52def mock_getpwnam(users, username):
53    return users[username]
54
55
56# Do not use these public keys, most of them are fetched from
57# the testdata for OpenSSH, and their private keys are available
58# https://github.com/openssh/openssh-portable/tree/master/regress/unittests/sshkey/testdata
59VALID_CONTENT = {
60    'dsa': (
61        "AAAAB3NzaC1kc3MAAACBAIrjOQSlSea19bExXBMBKBvcLhBoVvNBjCppNzllipF"
62        "W4jgIOMcNanULRrZGjkOKat6MWJNetSbV1E6IOFDQ16rQgsh/OvYU9XhzM8seLa"
63        "A21VszZuhIV7/2DE3vxu7B54zVzueG1O1Deq6goQCRGWBUnqO2yluJiG4HzrnDa"
64        "jzRAAAAFQDMPO96qXd4F5A+5b2f2MO7SpVomQAAAIBpC3K2zIbDLqBBs1fn7rsv"
65        "KcJvwihdlVjG7UXsDB76P2GNqVG+IlYPpJZ8TO/B/fzTMtrdXp9pSm9OY1+BgN4"
66        "REsZ2WNcvfgY33aWaEM+ieCcQigvxrNAF2FTVcbUIIxAn6SmHuQSWrLSfdHc8H7"
67        "hsrgeUPPdzjBD/cv2ZmqwZ1AAAAIAplIsScrJut5wJMgyK1JG0Kbw9JYQpLe95P"
68        "obB069g8+mYR8U0fysmTEdR44mMu0VNU5E5OhTYoTGfXrVrkR134LqFM2zpVVbE"
69        "JNDnIqDHxTkc6LY2vu8Y2pQ3/bVnllZZOda2oD5HQ7ovygQa6CH+fbaZHbdDUX/"
70        "5z7u2rVAlDw=="
71    ),
72    'ecdsa': (
73        "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBITrGBB3cgJ"
74        "J7fPxvtMW9H3oRisNpJ3OAslxZeyP7I0A9BPAW0RQIwHVtVnM7zrp4nI+JLZov/"
75        "Ql7lc2leWL7CY="
76    ),
77    'rsa': (
78        "AAAAB3NzaC1yc2EAAAABIwAAAQEA3I7VUf2l5gSn5uavROsc5HRDpZdQueUq5oz"
79        "emNSj8T7enqKHOEaFoU2VoPgGEWC9RyzSQVeyD6s7APMcE82EtmW4skVEgEGSbD"
80        "c1pvxzxtchBj78hJP6Cf5TCMFSXw+Fz5rF1dR23QDbN1mkHs7adr8GW4kSWqU7Q"
81        "7NDwfIrJJtO7Hi42GyXtvEONHbiRPOe8stqUly7MvUoN+5kfjBM8Qqpfl2+FNhT"
82        "YWpMfYdPUnE7u536WqzFmsaqJctz3gBxH9Ex7dFtrxR4qiqEr9Qtlu3xGn7Bw07"
83        "/+i1D+ey3ONkZLN+LQ714cgj8fRS4Hj29SCmXp5Kt5/82cD/VN3NtHw=="
84    ),
85    'ed25519': (
86        "AAAAC3NzaC1lZDI1NTE5AAAAIA1J77+CrJ8p6/vWCEzuylqJNMHUP/XmeYyGVWb"
87        "8lnDd"
88    ),
89    'ecdsa-sha2-nistp256-cert-v01@openssh.com': (
90        "AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAA"
91        "gQIfwT/+UX68/hlKsdKuaOuAVB6ftTg03SlP/uH4OBEwAAAAIbmlzdHAyNTYAAA"
92        "BBBEjA0gjJmPM6La3sXyfNlnjilvvGY6I2M8SvJj4o3X/46wcUbPWTaj4RF3EXw"
93        "HvNxplYBwdPlk2zEecvf9Cs2BMAAAAAAAAAAAAAAAEAAAAYa2V5cy9lY2RzYS1z"
94        "aGEyLW5pc3RwMjU2AAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJ"
95        "taXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW"
96        "5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtc"
97        "HR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAaAAAABNlY2RzYS1z"
98        "aGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRH6Y9Q1+ocQ8ETKW3LjQqtxg7"
99        "OuSSDacxmmQatQVaIawwjCbmntyEAqmVj3v9ElDSXnO5m7TyYMBQu4+vsh76RAA"
100        "AAZQAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAASgAAACEA47Cl2MMhr+glPGuxx"
101        "2tM3QXkDcwdP0SxSEW5yy4XV5oAAAAhANNMm1cdVlAt3hmycQgdD82zPlg5YvVO"
102        "iN0SQTbgVD8i"
103    ),
104    'ecdsa-sha2-nistp256': (
105        "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEjA0gjJmPM"
106        "6La3sXyfNlnjilvvGY6I2M8SvJj4o3X/46wcUbPWTaj4RF3EXwHvNxplYBwdPlk"
107        "2zEecvf9Cs2BM="
108    ),
109    'ecdsa-sha2-nistp384-cert-v01@openssh.com': (
110        "AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAA"
111        "grnSvDsK1EnCZndO1IyGWcGkVgVSkPWi/XO2ybPFyLVUAAAAIbmlzdHAzODQAAA"
112        "BhBAaYSQs+8TT0Tzciy0dorwhur6yzOGUrYQ6ueUQYWbE7eNdHmhsVrlpGPgSaY"
113        "ByhXtAJiPOMqLU5h0eb3sCtM3ek4NvjXFTGTqPrrxJI6q0OsgrtkGE7UM9ZsfMm"
114        "7q6BOAAAAAAAAAAAAAAAAQAAABhrZXlzL2VjZHNhLXNoYTItbmlzdHAzODQAAAA"
115        "AAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZG"
116        "luZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pd"
117        "C1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1p"
118        "dC11c2VyLXJjAAAAAAAAAAAAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAA"
119        "IbmlzdHAzODQAAABhBLWbubcMzcWc7lMTCMGVXZlaVvUOHLjpr6SOOScFFrd8K9"
120        "Gl8nYELST5HZ1gym65m+MG6/tbrUWIY/flLWNIe+WtqxrdPPGdIhFruCwNw2peZ"
121        "SbQOa/o3AGnJ/vO6EKEGAAAAIQAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAGkA"
122        "AAAxAL10JHd5bvnbpD+fet/k1YE1BEIrqGXaoIIJ9ReE5H4nTK1uQJzMD7+wwGK"
123        "RVYqYQgAAADAiit0UCMDAUbjD+R2x4LvU3x/t8G3sdqDLRNfMRpjZpvcS8AwC+Y"
124        "VFVSQNn0AyzW0="
125    ),
126    'ecdsa-sha2-nistp384': (
127        "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBAaYSQs+8TT"
128        "0Tzciy0dorwhur6yzOGUrYQ6ueUQYWbE7eNdHmhsVrlpGPgSaYByhXtAJiPOMqL"
129        "U5h0eb3sCtM3ek4NvjXFTGTqPrrxJI6q0OsgrtkGE7UM9ZsfMm7q6BOA=="
130    ),
131    'ecdsa-sha2-nistp521-cert-v01@openssh.com': (
132        "AAAAKGVjZHNhLXNoYTItbmlzdHA1MjEtY2VydC12MDFAb3BlbnNzaC5jb20AAAA"
133        "gGmRzkkMvRFk1V5U3m3mQ2nfW20SJVXk1NKnT5iZGDcEAAAAIbmlzdHA1MjEAAA"
134        "CFBAHosAOHAI1ZkerbKYQ72S6uit1u77PCj/OalZtXgsxv0TTAZB273puG2X94C"
135        "Q8yyNHcby87zFZHdv5BSKyZ/cyREAAeiAcSakop9VS3+bUfZpEIqwBZXarwUjnR"
136        "nxprkcQ0rfCCdagkGZr/OA7DemK2D8tKLTHsKoEEWNImo6/pXDkFxAAAAAAAAAA"
137        "AAAAAAQAAABhrZXlzL2VjZHNhLXNoYTItbmlzdHA1MjEAAAAAAAAAAAAAAAD///"
138        "///////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXc"
139        "GVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndh"
140        "cmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAA"
141        "AAAAAAAAAAACsAAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAA"
142        "CFBAC6hFVXM1XEg/7qKkp5sLZuANGQVW88b5pPn2ZcK0td9IQstLH6BwWuZ6MPE"
143        "ogiDlvx9HD1BaKGBBfkxgOY8NGFzQHbjU9eTWH3gt0RATDbZsij1pSkFPnAXdU9"
144        "SjfogYloI2xdHaTCgWp3zgsUV+BBQ0QGGv2MqqcOmrF0f5YEJeOffAAAAKcAAAA"
145        "TZWNkc2Etc2hhMi1uaXN0cDUyMQAAAIwAAABCAT+vSOYPuYVTDopDW08576d5Sb"
146        "edXQMOu1op4CQIm98VKtAXvu5dfioi5VYAqpte8M+UxEMOMiQWJp+U9exYf6LuA"
147        "AAAQgEzkIpX3yKXPaPcK17mNx40ujEDitm4ARmbhAge0sFhZtf7YIgI55b6vkI8"
148        "JvMJkzQCBF1cpNOaIpVh1nFZNBphMQ=="
149    ),
150    'ecdsa-sha2-nistp521': (
151        "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHosAOHAI1"
152        "ZkerbKYQ72S6uit1u77PCj/OalZtXgsxv0TTAZB273puG2X94CQ8yyNHcby87zF"
153        "ZHdv5BSKyZ/cyREAAeiAcSakop9VS3+bUfZpEIqwBZXarwUjnRnxprkcQ0rfCCd"
154        "agkGZr/OA7DemK2D8tKLTHsKoEEWNImo6/pXDkFxA=="
155    ),
156    'sk-ecdsa-sha2-nistp256-cert-v01@openssh.com': (
157        "AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIxzuxl4z3u"
158        "wAIslne8Huft+1n1IhHAlNbWZkQyyECCGAAAAIFOG6kY7Rf4UtCFvPwKgo/BztX"
159        "ck2xC4a2WyA34XtIwZAAAAAAAAAAgAAAACAAAABmp1bGl1cwAAABIAAAAFaG9zd"
160        "DEAAAAFaG9zdDIAAAAANowB8AAAAABNHmBwAAAAAAAAAAAAAAAAAAAAMwAAAAtz"
161        "c2gtZWQyNTUxOQAAACBThupGO0X+FLQhbz8CoKPwc7V3JNsQuGtlsgN+F7SMGQA"
162        "AAFMAAAALc3NoLWVkMjU1MTkAAABABGTn+Bmz86Ajk+iqKCSdP5NClsYzn4alJd"
163        "0V5bizhP0Kumc/HbqQfSt684J1WdSzih+EjvnTgBhK9jTBKb90AQ=="
164    ),
165    'sk-ecdsa-sha2-nistp256@openssh.com': (
166        "AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHA"
167        "yNTYAAABBBIELQJ2DgvaX1yQlKFokfWM2suuaCFI2qp0eJodHyg6O4ifxc3XpRK"
168        "d1OS8dNYQtE/YjdXSrA+AOnMF5ns2Nkx4AAAAEc3NoOg=="
169    ),
170    'sk-ssh-ed25519-cert-v01@openssh.com': (
171        "AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIxzuxl4z3u"
172        "wAIslne8Huft+1n1IhHAlNbWZkQyyECCGAAAAIFOG6kY7Rf4UtCFvPwKgo/BztX"
173        "ck2xC4a2WyA34XtIwZAAAAAAAAAAgAAAACAAAABmp1bGl1cwAAABIAAAAFaG9zd"
174        "DEAAAAFaG9zdDIAAAAANowB8AAAAABNHmBwAAAAAAAAAAAAAAAAAAAAMwAAAAtz"
175        "c2gtZWQyNTUxOQAAACBThupGO0X+FLQhbz8CoKPwc7V3JNsQuGtlsgN+F7SMGQA"
176        "AAFMAAAALc3NoLWVkMjU1MTkAAABABGTn+Bmz86Ajk+iqKCSdP5NClsYzn4alJd"
177        "0V5bizhP0Kumc/HbqQfSt684J1WdSzih+EjvnTgBhK9jTBKb90AQ=="
178    ),
179    'sk-ssh-ed25519@openssh.com': (
180        "AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAICFo/k5LU8863u66YC9"
181        "eUO2170QduohPURkQnbLa/dczAAAABHNzaDo="
182    ),
183    'ssh-dss-cert-v01@openssh.com': (
184        "AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgdTlbNU9Hn9Qng3F"
185        "HxwH971bxCIoq1ern/QWFFDWXgmYAAACBAPqS600VGwdPAQC/p3f0uGyrLVql0c"
186        "Fn1zYd/JGvtabKnIYjLaYprje/NcjwI3CZFJiz4Dp3S8kLs+X5/1DMn/Tg1Y4D4"
187        "yLB+6vCtHcJF7rVBFhvw/KZwc7G54ez3khyOtsg82fzpyOc8/mq+/+C5TMKO7DD"
188        "jMF0k5emWKCsa3ZfAAAAFQCjA/+dKkMu4/CWjJPtfl7YNaStNQAAAIEA7uX1BVV"
189        "tJKjLmWrpw62+l/xSXA5rr7MHBuWjiCYV3VHBfXJaQDyRDtGuEJKDwdzqYgacpG"
190        "ApGWL/cuBtJ9nShsUl6GRG0Ra03g+Hx9VR5LviJBsjAVB4qVgciU1NGga0Bt2Le"
191        "cd1X4EGQRBzVXeuOpiqGM6jP/I2yDMs0Pboet0AAACBAOdXpyfmobEBaOqZAuvg"
192        "j1P0uhjG2P31Ufurv22FWPBU3A9qrkxbOXwE0LwvjCvrsQV/lrYhJz/tiys40Ve"
193        "ahulWZE5SAHMXGIf95LiLSgaXMjko7joot+LK84ltLymwZ4QMnYjnZSSclf1Uuy"
194        "QMcUtb34+I0u9Ycnyhp2mSFsQtAAAAAAAAAAYAAAACAAAABmp1bGl1cwAAABIAA"
195        "AAFaG9zdDEAAAAFaG9zdDIAAAAANowB8AAAAABNHmBwAAAAAAAAAAAAAAAAAAAA"
196        "MwAAAAtzc2gtZWQyNTUxOQAAACBThupGO0X+FLQhbz8CoKPwc7V3JNsQuGtlsgN"
197        "+F7SMGQAAAFMAAAALc3NoLWVkMjU1MTkAAABAh/z1LIdNL1b66tQ8t9DY9BTB3B"
198        "QKpTKmc7ezyFKLwl96yaIniZwD9Ticdbe/8i/Li3uCFE3EAt8NAIv9zff8Bg=="
199    ),
200    'ssh-dss': (
201        "AAAAB3NzaC1kc3MAAACBAPqS600VGwdPAQC/p3f0uGyrLVql0cFn1zYd/JGvtab"
202        "KnIYjLaYprje/NcjwI3CZFJiz4Dp3S8kLs+X5/1DMn/Tg1Y4D4yLB+6vCtHcJF7"
203        "rVBFhvw/KZwc7G54ez3khyOtsg82fzpyOc8/mq+/+C5TMKO7DDjMF0k5emWKCsa"
204        "3ZfAAAAFQCjA/+dKkMu4/CWjJPtfl7YNaStNQAAAIEA7uX1BVVtJKjLmWrpw62+"
205        "l/xSXA5rr7MHBuWjiCYV3VHBfXJaQDyRDtGuEJKDwdzqYgacpGApGWL/cuBtJ9n"
206        "ShsUl6GRG0Ra03g+Hx9VR5LviJBsjAVB4qVgciU1NGga0Bt2Lecd1X4EGQRBzVX"
207        "euOpiqGM6jP/I2yDMs0Pboet0AAACBAOdXpyfmobEBaOqZAuvgj1P0uhjG2P31U"
208        "furv22FWPBU3A9qrkxbOXwE0LwvjCvrsQV/lrYhJz/tiys40VeahulWZE5SAHMX"
209        "GIf95LiLSgaXMjko7joot+LK84ltLymwZ4QMnYjnZSSclf1UuyQMcUtb34+I0u9"
210        "Ycnyhp2mSFsQt"
211    ),
212    'ssh-ed25519-cert-v01@openssh.com': (
213        "AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIxzuxl4z3u"
214        "wAIslne8Huft+1n1IhHAlNbWZkQyyECCGAAAAIFOG6kY7Rf4UtCFvPwKgo/BztX"
215        "ck2xC4a2WyA34XtIwZAAAAAAAAAAgAAAACAAAABmp1bGl1cwAAABIAAAAFaG9zd"
216        "DEAAAAFaG9zdDIAAAAANowB8AAAAABNHmBwAAAAAAAAAAAAAAAAAAAAMwAAAAtz"
217        "c2gtZWQyNTUxOQAAACBThupGO0X+FLQhbz8CoKPwc7V3JNsQuGtlsgN+F7SMGQA"
218        "AAFMAAAALc3NoLWVkMjU1MTkAAABABGTn+Bmz86Ajk+iqKCSdP5NClsYzn4alJd"
219        "0V5bizhP0Kumc/HbqQfSt684J1WdSzih+EjvnTgBhK9jTBKb90AQ=="
220    ),
221    'ssh-ed25519': (
222        "AAAAC3NzaC1lZDI1NTE5AAAAIFOG6kY7Rf4UtCFvPwKgo/BztXck2xC4a2WyA34"
223        "XtIwZ"
224    ),
225    'ssh-rsa-cert-v01@openssh.com': (
226        "AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg98LhS2EHxLOWCLo"
227        "pZPwHdg/RJXusnkOqQXSc9R7aITkAAAADAQABAAAAgQDLV5lUTt7FrADseB/CGh"
228        "EZzpoojjEW5y8+ePvLppmK3MmMI18ud6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yM"
229        "rW6wb84gbq8C31Xoe9EORcIUuGSvDKdNSM1SjlhDquRblDFB8kToqXyx1lqrXec"
230        "XylxIUOL0jE+u0rU1967pDJx+wAAAAAAAAAFAAAAAgAAAAZqdWxpdXMAAAASAAA"
231        "ABWhvc3QxAAAABWhvc3QyAAAAADaMAfAAAAAATR5gcAAAAAAAAAAAAAAAAAAAAD"
232        "MAAAALc3NoLWVkMjU1MTkAAAAgU4bqRjtF/hS0IW8/AqCj8HO1dyTbELhrZbIDf"
233        "he0jBkAAABTAAAAC3NzaC1lZDI1NTE5AAAAQI3QGlUCzC07KorupxpDkkGy6tni"
234        "aZ8EvBflzvv+itXWNchGvfUeHmVT6aX0sRqehdz/lR+GmXRoZBhofwh0qAM="
235    ),
236    'ssh-rsa': (
237        "AAAAB3NzaC1yc2EAAAADAQABAAAAgQDLV5lUTt7FrADseB/CGhEZzpoojjEW5y8"
238        "+ePvLppmK3MmMI18ud6vxzpK3bwZLYkVSyfJYI0HmIuGhdu7yMrW6wb84gbq8C3"
239        "1Xoe9EORcIUuGSvDKdNSM1SjlhDquRblDFB8kToqXyx1lqrXecXylxIUOL0jE+u"
240        "0rU1967pDJx+w=="
241    ),
242    'ssh-xmss-cert-v01@openssh.com': (
243        "AAAAHXNzaC14bXNzLWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIM2UD0IH+Igsekq"
244        "xjTO5f36exX4WGRMCtDGPjwfbXblxAAAAFVhNU1NfU0hBMi0yNTZfVzE2X0gxMA"
245        "AAAEDI83/K5JMOy0BMJgQypRdz35ApAnoQinMJ8ZMoZPaEJF8Z4rANQlfzaAXum"
246        "N3RDU5CGIUGGw+WJ904G/wwEq9CAAAAAAAAAAAAAAABAAAACWtleXMveG1zcwAA"
247        "AAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJ"
248        "kaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybW"
249        "l0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVyb"
250        "Wl0LXVzZXItcmMAAAAAAAAAAAAAAHUAAAAUc3NoLXhtc3NAb3BlbnNzaC5jb20A"
251        "AAAVWE1TU19TSEEyLTI1Nl9XMTZfSDEwAAAAQA+irIyT2kaOd07YWZT/QItzNBZ"
252        "kUYwnqZJihQ7BxuyiDP4HEFbnfYnnIZXx9Asyi7vDyZRvi+AMSOzmMSq4JnkAAA"
253        "ngAAAAFHNzaC14bXNzQG9wZW5zc2guY29tAAAJxAAAAAAFjaKTDc+7Hu2uFGIab"
254        "3NAku8HbbGtrq/uGXOxmqxu4RaLqmwofl5iXk3nMwWEhQAb99vAc9D9ZFtfxJO4"
255        "STYUTjbj4BxToov/uvbYfE5VeO6sMvkGglgh9YHkCTAItsG8EmGT1SIPfKYzLlN"
256        "jvUlbcv0PaPFMJ0wzS9mNfuRf+KUhf3dxQ6zaMrBH3KEJ8Me2kNjhnh6rNPROeI"
257        "N+IcStSKsydYuiySGKS/orsH38XysuK5QqLizbHJY3cqLbkW9LsIijb+pfEJh4Y"
258        "bOoAbraWAv9ySnWCyRhvw2x8uJ0ZM+p5WSRiZfB3JxCpOhHgiKa9TdmdjnAtnED"
259        "zqKOj/gM7y9mesn5ydQI0bENOGymlw0ThUGKbXMxn87Hc9dDPURUBmoO3NGjPDf"
260        "7meS39A1ZEGtCe/pbZU9iwxqGx4wJYvB4lutRP2tYC1pA6hjQCcHibvxl5iqj+1"
261        "jRjwPr8dbTm4PdETW/7JDSVQXKjxOT0kRLHLelJNeviGx5zSHR5PtnUP3nOBMme"
262        "hk9DwcQW9vfKeWSnu9CMnF8xvYJxoPKQwmz0TKo+YVOUnc9/Ma+Ykseof9/W+rk"
263        "USQGELc4x7XE5XBKYZZP2PmtxirQ3qTWFw+CeTX2Oa+jPYkzOa7jgmHJ3Fi9Xqw"
264        "3L844vRl97e28GmwS0M1SXH+ohES0mO4EcrGh5OLyXBaRTV5QMo+4Bg6FH/HwEn"
265        "gG1mdEOAqvctK2QC70c4lHGzfexqwQ2U6WUADPcd/BLOE8Noj1EiXYwZrSA1okZ"
266        "FYnS/b89Uo51D2FE4A33V4gcxAglGzVNtrPulkguNT9B4jjNgdIwkTBL9k3ujkG"
267        "og6pyYjZ0J5Jp5XPBn+y0LqrpOdZijzrc1OJbX59tTeIbDkM7Fw8As4a03hQPDU"
268        "FTOdyMHgLnuLhLXOcqIjvW5axZL/Kx3UET8wrSHizPoa6NErCG4v5mC2M4kBSOW"
269        "In1QV27QMaHkL/ZAa3mPsW5iFZtOVEGzw2BW4MZs0qOrcloCENZzOHiMBroKEkH"
270        "AbzX6D1FLwml2JpXq4JXlCrdIiFm4+co5ygnWPqb4QGzMlcbjW/x/A16TthNuok"
271        "wwlmK5ndKZ76LahyGKEwx2Nv0D+0xilEC1EldtiYRdBNlcGbU/A5EhH5bQ9KVIH"
272        "wjWm35pRPLl5224//nqvQKhwFCn9otsR35XHXev3IQ0or3HmQxIvSDOwir1l66z"
273        "FFrkyHMWexoucbTBxw1MN3hLb247lcVYJ5+hspJgyoYbfR5RkQVDzhpzskogP7l"
274        "K5t0bphu+f+hpvrca7DAiiIZkcR4R1UUQoRnJPRXyXOxlxwS10b51cP9p9jzvZj"
275        "d2LUs8yx1KXWSxNHo6WmtYONNaUfdX2OB5+QCvPULfLfFeBrqpX6Yp5wQMM5Cup"
276        "k8FEfV07eEgQkVE9nDGKHglWo3kUdOF+XCqWAnXn0b/2bNS9/SSAz6gB1GTFcN/"
277        "QsFGlC0QgbCJbQ7LQM6hilRWupWvN5zZ/+HJyyRHuSs5VnQnKiGbIa6AIhx7mP7"
278        "8T82gKjU3mHLJWMGKcT3cY8R958Gs+w4OT71VJRMw3kK6qk02WCbD5OtbFeC6ib"
279        "KRJKdLK3BzjVs/Fzu3mHVucVby3jpvG1Z8HKspKFhvV7gjFEPu8qHKi4MdAlif/"
280        "KakyPk8yZB/dMfaxh7Kv/WpJuSwWNs7RNh29e+ZG+POxqRPWiHqiVw7P17a4dN7"
281        "nkVOawdBEyxI4NAY+4zW+0r0bAy6zNBitBvkq3IXfr3De6Upex52sPHvK04PXoV"
282        "RI6gjnpPSbLLjpSpcHPKgB7DWefLfhd63BUQbc57D8zm8Jd6qtmzcSKn+wz5/zT"
283        "0I6v9I4a+DOjjyqpPpzzNU76pt+Y8SuBgHzMm1vcAdNWlbQrqtScvm0T9AkYni6"
284        "47vSh77uwRZKDtMCMSU151tVUavXhtLYLZ6/ll5NhMXkkx8//i7pk1OBjN5LHVQ"
285        "0QeimRmavlXU1dJ2rwsFAV+9dDdJXUNOq3VLTo9FrbOzZiWtzzjkJpVJAFREnBn"
286        "yIDBK5AXtXE1RzfzaBHzbI2e2kO3t+CSNLWYMFYHBDqaeICYQ9+I9aO/8hnzVSo"
287        "fp+8IfWO8iJhppqynUniicW2oCzrn4oczzYNEjImt8CGY7g90GxWfX+ZgXMJfy/"
288        "bQiFQL3dZvVypDHEbFoIGz+sxkL83xrP4MZV1V9Wwa64lDXYv01Kp4kQXmmnAZY"
289        "KlxBoWqYDXLeLLguSOZxDSCIDpd+YPm39wQ3wOysHW2fmsWtp6FPPlQRUYjsGIP"
290        "lfrkJzpoeaPKDtF1m+mOULfEh9kvTKCmKRi385T9ON39D97eWqaM4CCfUGImvdR"
291        "DlZLXvjmaAh5BVJ8VJxk75OkP14vWFFlTMv0/k4BYLDKsrNqCREC/G9nQBGcD2D"
292        "CLwC2zPNaX2Y9dnyDs2csjN1ibsYttUMnXMgBcnCOkIkVS496Bpc0jQMf35GUgb"
293        "PSyliwqCoXjEBP/2eyq0VLFKQ0fXGsHWvElT+Y/7RYNTiYVWttFMxN5H/2EGcgn"
294        "lfNHLpQvXH9u/3YminS9GX30hQ7jFhpHXxkK8gZ1mpHL9K3pfKS3lG6EF9wQ23O"
295        "qS8m995SG3dp3MzmywxXen/ukXx6bDiEl5VaOvdRUcbhr5Eb3exVDfdWiaJdTYF"
296        "WfIfJOWx88drB3J9vFwjmuaoNEOjFsoNAMYthYOxXraXaJblvmUKz6tJ3T8/G7x"
297        "B9QGYNBsOqBolKoKHBtsWCosLdWhEZr9VFFh2AJrOW1fx24CIkHnvfTtwYORvQq"
298        "Ckuq2bZS1EOdsFkU/X5gwPl6gSUTNhV3IooXkBFL3iBEbfZ6JpQHVVyIuNWjIyN"
299        "b2liCn9Nn0VHeNMMRLl7uyw4eKlOX2ogom8SLvihYxcJoqlCwtehpLsKsU4iwME"
300        "PmDteW5GBGf4GbnqPFkpIT5ed1jGhdZt/dpsp+v6QhYH1uX4pPxdkdnuc84/yb9"
301        "k4SQdKBJ+l3KZkfIxApNWOZqicJfz/eWwS/15hiamRKRuiiUV2zS1V+l8bV7g9O"
302        "gy5scPBMONxtfFlGEKikZKurFmzboCOGQKRBEUCpsY44IAp443h59pQdVIb0YAS"
303        "kfp2xKHwYij6ELRNdH5MrlFa3bNTskGO4k5XDR4cl/Sma2SXgBKb5XjTtlNmCQG"
304        "Gv6lOW7pGXNhs5wfd8K9Ukm6KeLTIlYn1iiKM37YQpa+4JQYljCYhumbqNCkPTZ"
305        "rNYClh8fQEQ8XuOCDpomMWu58YOTfbZNMDWs/Ou7RfCjX+VNwjPShDK9joMwWKc"
306        "Jy3QalZbaoWtcyyvXxR2sqhVR9F7Cmasq4="
307    ),
308    'ssh-xmss@openssh.com': (
309        "AAAAFHNzaC14bXNzQG9wZW5zc2guY29tAAAAFVhNU1NfU0hBMi0yNTZfVzE2X0g"
310        "xMAAAAECqptWnK94d+Sj2xcdTu8gz+75lawZoLSZFqC5IhbYuT/Z3oBZCim6yt+"
311        "HAmk6MKldl3Fg+74v4sR/SII0I0Jv/"
312    ),
313}
314
315KEY_TYPES = list(VALID_CONTENT.keys())
316
317TEST_OPTIONS = (
318    "no-port-forwarding,no-agent-forwarding,no-X11-forwarding,"
319    'command="echo \'Please login as the user \"ubuntu\" rather than the'
320    'user \"root\".\';echo;sleep 10"')
321
322
323class TestAuthKeyLineParser(test_helpers.CiTestCase):
324
325    def test_simple_parse(self):
326        # test key line with common 3 fields (keytype, base64, comment)
327        parser = ssh_util.AuthKeyLineParser()
328        for ktype in KEY_TYPES:
329            content = VALID_CONTENT[ktype]
330            comment = 'user-%s@host' % ktype
331            line = ' '.join((ktype, content, comment,))
332            key = parser.parse(line)
333
334            self.assertEqual(key.base64, content)
335            self.assertFalse(key.options)
336            self.assertEqual(key.comment, comment)
337            self.assertEqual(key.keytype, ktype)
338
339    def test_parse_no_comment(self):
340        # test key line with key type and base64 only
341        parser = ssh_util.AuthKeyLineParser()
342        for ktype in KEY_TYPES:
343            content = VALID_CONTENT[ktype]
344            line = ' '.join((ktype, content,))
345            key = parser.parse(line)
346
347            self.assertEqual(key.base64, content)
348            self.assertFalse(key.options)
349            self.assertFalse(key.comment)
350            self.assertEqual(key.keytype, ktype)
351
352    def test_parse_with_keyoptions(self):
353        # test key line with options in it
354        parser = ssh_util.AuthKeyLineParser()
355        options = TEST_OPTIONS
356        for ktype in KEY_TYPES:
357            content = VALID_CONTENT[ktype]
358            comment = 'user-%s@host' % ktype
359            line = ' '.join((options, ktype, content, comment,))
360            key = parser.parse(line)
361
362            self.assertEqual(key.base64, content)
363            self.assertEqual(key.options, options)
364            self.assertEqual(key.comment, comment)
365            self.assertEqual(key.keytype, ktype)
366
367    def test_parse_with_options_passed_in(self):
368        # test key line with key type and base64 only
369        parser = ssh_util.AuthKeyLineParser()
370
371        baseline = ' '.join(("rsa", VALID_CONTENT['rsa'], "user@host"))
372        myopts = "no-port-forwarding,no-agent-forwarding"
373
374        key = parser.parse("allowedopt" + " " + baseline)
375        self.assertEqual(key.options, "allowedopt")
376
377        key = parser.parse("overridden_opt " + baseline, options=myopts)
378        self.assertEqual(key.options, myopts)
379
380    def test_parse_invalid_keytype(self):
381        parser = ssh_util.AuthKeyLineParser()
382        key = parser.parse(' '.join(["badkeytype", VALID_CONTENT['rsa']]))
383
384        self.assertFalse(key.valid())
385
386
387class TestUpdateAuthorizedKeys(test_helpers.CiTestCase):
388
389    def test_new_keys_replace(self):
390        """new entries with the same base64 should replace old."""
391        orig_entries = [
392            ' '.join(('rsa', VALID_CONTENT['rsa'], 'orig_comment1')),
393            ' '.join(('dsa', VALID_CONTENT['dsa'], 'orig_comment2'))]
394
395        new_entries = [
396            ' '.join(('rsa', VALID_CONTENT['rsa'], 'new_comment1')), ]
397
398        expected = '\n'.join([new_entries[0], orig_entries[1]]) + '\n'
399
400        parser = ssh_util.AuthKeyLineParser()
401        found = ssh_util.update_authorized_keys(
402            [parser.parse(p) for p in orig_entries],
403            [parser.parse(p) for p in new_entries])
404
405        self.assertEqual(expected, found)
406
407    def test_new_invalid_keys_are_ignored(self):
408        """new entries that are invalid should be skipped."""
409        orig_entries = [
410            ' '.join(('rsa', VALID_CONTENT['rsa'], 'orig_comment1')),
411            ' '.join(('dsa', VALID_CONTENT['dsa'], 'orig_comment2'))]
412
413        new_entries = [
414            ' '.join(('rsa', VALID_CONTENT['rsa'], 'new_comment1')),
415            'xxx-invalid-thing1',
416            'xxx-invalid-blob2'
417        ]
418
419        expected = '\n'.join([new_entries[0], orig_entries[1]]) + '\n'
420
421        parser = ssh_util.AuthKeyLineParser()
422        found = ssh_util.update_authorized_keys(
423            [parser.parse(p) for p in orig_entries],
424            [parser.parse(p) for p in new_entries])
425
426        self.assertEqual(expected, found)
427
428
429class TestParseSSHConfig(test_helpers.CiTestCase):
430
431    def setUp(self):
432        self.load_file_patch = patch('cloudinit.ssh_util.util.load_file')
433        self.load_file = self.load_file_patch.start()
434        self.isfile_patch = patch('cloudinit.ssh_util.os.path.isfile')
435        self.isfile = self.isfile_patch.start()
436        self.isfile.return_value = True
437
438    def tearDown(self):
439        self.load_file_patch.stop()
440        self.isfile_patch.stop()
441
442    def test_not_a_file(self):
443        self.isfile.return_value = False
444        self.load_file.side_effect = IOError
445        ret = ssh_util.parse_ssh_config('not a real file')
446        self.assertEqual([], ret)
447
448    def test_empty_file(self):
449        self.load_file.return_value = ''
450        ret = ssh_util.parse_ssh_config('some real file')
451        self.assertEqual([], ret)
452
453    def test_comment_line(self):
454        comment_line = '# This is a comment'
455        self.load_file.return_value = comment_line
456        ret = ssh_util.parse_ssh_config('some real file')
457        self.assertEqual(1, len(ret))
458        self.assertEqual(comment_line, ret[0].line)
459
460    def test_blank_lines(self):
461        lines = ['', '\t', ' ']
462        self.load_file.return_value = '\n'.join(lines)
463        ret = ssh_util.parse_ssh_config('some real file')
464        self.assertEqual(len(lines), len(ret))
465        for line in ret:
466            self.assertEqual('', line.line)
467
468    def test_lower_case_config(self):
469        self.load_file.return_value = 'foo bar'
470        ret = ssh_util.parse_ssh_config('some real file')
471        self.assertEqual(1, len(ret))
472        self.assertEqual('foo', ret[0].key)
473        self.assertEqual('bar', ret[0].value)
474
475    def test_upper_case_config(self):
476        self.load_file.return_value = 'Foo Bar'
477        ret = ssh_util.parse_ssh_config('some real file')
478        self.assertEqual(1, len(ret))
479        self.assertEqual('foo', ret[0].key)
480        self.assertEqual('Bar', ret[0].value)
481
482    def test_lower_case_with_equals(self):
483        self.load_file.return_value = 'foo=bar'
484        ret = ssh_util.parse_ssh_config('some real file')
485        self.assertEqual(1, len(ret))
486        self.assertEqual('foo', ret[0].key)
487        self.assertEqual('bar', ret[0].value)
488
489    def test_upper_case_with_equals(self):
490        self.load_file.return_value = 'Foo=bar'
491        ret = ssh_util.parse_ssh_config('some real file')
492        self.assertEqual(1, len(ret))
493        self.assertEqual('foo', ret[0].key)
494        self.assertEqual('bar', ret[0].value)
495
496
497class TestUpdateSshConfigLines(test_helpers.CiTestCase):
498    """Test the update_ssh_config_lines method."""
499    exlines = [
500        "#PasswordAuthentication yes",
501        "UsePAM yes",
502        "# Comment line",
503        "AcceptEnv LANG LC_*",
504        "X11Forwarding no",
505    ]
506    pwauth = "PasswordAuthentication"
507
508    def check_line(self, line, opt, val):
509        self.assertEqual(line.key, opt.lower())
510        self.assertEqual(line.value, val)
511        self.assertIn(opt, str(line))
512        self.assertIn(val, str(line))
513
514    def test_new_option_added(self):
515        """A single update of non-existing option."""
516        lines = ssh_util.parse_ssh_config_lines(list(self.exlines))
517        result = ssh_util.update_ssh_config_lines(lines, {'MyKey': 'MyVal'})
518        self.assertEqual(['MyKey'], result)
519        self.check_line(lines[-1], "MyKey", "MyVal")
520
521    def test_commented_out_not_updated_but_appended(self):
522        """Implementation does not un-comment and update lines."""
523        lines = ssh_util.parse_ssh_config_lines(list(self.exlines))
524        result = ssh_util.update_ssh_config_lines(lines, {self.pwauth: "no"})
525        self.assertEqual([self.pwauth], result)
526        self.check_line(lines[-1], self.pwauth, "no")
527
528    def test_option_without_value(self):
529        """Implementation only accepts key-value pairs."""
530        extended_exlines = self.exlines.copy()
531        denyusers_opt = "DenyUsers"
532        extended_exlines.append(denyusers_opt)
533        lines = ssh_util.parse_ssh_config_lines(list(extended_exlines))
534        self.assertNotIn(denyusers_opt, str(lines))
535
536    def test_single_option_updated(self):
537        """A single update should have change made and line updated."""
538        opt, val = ("UsePAM", "no")
539        lines = ssh_util.parse_ssh_config_lines(list(self.exlines))
540        result = ssh_util.update_ssh_config_lines(lines, {opt: val})
541        self.assertEqual([opt], result)
542        self.check_line(lines[1], opt, val)
543
544    def test_multiple_updates_with_add(self):
545        """Verify multiple updates some added some changed, some not."""
546        updates = {"UsePAM": "no", "X11Forwarding": "no", "NewOpt": "newval",
547                   "AcceptEnv": "LANG ADD LC_*"}
548        lines = ssh_util.parse_ssh_config_lines(list(self.exlines))
549        result = ssh_util.update_ssh_config_lines(lines, updates)
550        self.assertEqual(set(["UsePAM", "NewOpt", "AcceptEnv"]), set(result))
551        self.check_line(lines[3], "AcceptEnv", updates["AcceptEnv"])
552
553    def test_return_empty_if_no_changes(self):
554        """If there are no changes, then return should be empty list."""
555        updates = {"UsePAM": "yes"}
556        lines = ssh_util.parse_ssh_config_lines(list(self.exlines))
557        result = ssh_util.update_ssh_config_lines(lines, updates)
558        self.assertEqual([], result)
559        self.assertEqual(self.exlines, [str(line) for line in lines])
560
561    def test_keycase_not_modified(self):
562        """Original case of key should not be changed on update.
563        This behavior is to keep original config as much intact as can be."""
564        updates = {"usepam": "no"}
565        lines = ssh_util.parse_ssh_config_lines(list(self.exlines))
566        result = ssh_util.update_ssh_config_lines(lines, updates)
567        self.assertEqual(["usepam"], result)
568        self.assertEqual("UsePAM no", str(lines[1]))
569
570
571class TestUpdateSshConfig(test_helpers.CiTestCase):
572    cfgdata = '\n'.join(["#Option val", "MyKey ORIG_VAL", ""])
573
574    def test_modified(self):
575        mycfg = self.tmp_path("ssh_config_1")
576        util.write_file(mycfg, self.cfgdata)
577        ret = ssh_util.update_ssh_config({"MyKey": "NEW_VAL"}, mycfg)
578        self.assertTrue(ret)
579        found = util.load_file(mycfg)
580        self.assertEqual(self.cfgdata.replace("ORIG_VAL", "NEW_VAL"), found)
581        # assert there is a newline at end of file (LP: #1677205)
582        self.assertEqual('\n', found[-1])
583
584    def test_not_modified(self):
585        mycfg = self.tmp_path("ssh_config_2")
586        util.write_file(mycfg, self.cfgdata)
587        with patch("cloudinit.ssh_util.util.write_file") as m_write_file:
588            ret = ssh_util.update_ssh_config({"MyKey": "ORIG_VAL"}, mycfg)
589        self.assertFalse(ret)
590        self.assertEqual(self.cfgdata, util.load_file(mycfg))
591        m_write_file.assert_not_called()
592
593
594class TestBasicAuthorizedKeyParse(test_helpers.CiTestCase):
595    def test_user(self):
596        self.assertEqual(
597            ["/opt/bobby/keys"],
598            ssh_util.render_authorizedkeysfile_paths(
599                "/opt/%u/keys", "/home/bobby", "bobby"))
600
601    def test_user_file(self):
602        self.assertEqual(
603            ["/opt/bobby"],
604            ssh_util.render_authorizedkeysfile_paths(
605                "/opt/%u", "/home/bobby", "bobby"))
606
607    def test_user_file2(self):
608        self.assertEqual(
609            ["/opt/bobby/bobby"],
610            ssh_util.render_authorizedkeysfile_paths(
611                "/opt/%u/%u", "/home/bobby", "bobby"))
612
613    def test_multiple(self):
614        self.assertEqual(
615            ["/keys/path1", "/keys/path2"],
616            ssh_util.render_authorizedkeysfile_paths(
617                "/keys/path1 /keys/path2", "/home/bobby", "bobby"))
618
619    def test_multiple2(self):
620        self.assertEqual(
621            ["/keys/path1", "/keys/bobby"],
622            ssh_util.render_authorizedkeysfile_paths(
623                "/keys/path1 /keys/%u", "/home/bobby", "bobby"))
624
625    def test_relative(self):
626        self.assertEqual(
627            ["/home/bobby/.secret/keys"],
628            ssh_util.render_authorizedkeysfile_paths(
629                ".secret/keys", "/home/bobby", "bobby"))
630
631    def test_home(self):
632        self.assertEqual(
633            ["/homedirs/bobby/.keys"],
634            ssh_util.render_authorizedkeysfile_paths(
635                "%h/.keys", "/homedirs/bobby", "bobby"))
636
637    def test_all(self):
638        self.assertEqual(
639            ["/homedirs/bobby/.keys", "/homedirs/bobby/.secret/keys",
640             "/keys/path1", "/opt/bobby/keys"],
641            ssh_util.render_authorizedkeysfile_paths(
642                "%h/.keys .secret/keys /keys/path1 /opt/%u/keys",
643                "/homedirs/bobby", "bobby"))
644
645
646class TestMultipleSshAuthorizedKeysFile(test_helpers.CiTestCase):
647
648    def create_fake_users(self, names, mock_permissions,
649                          m_get_group, m_get_owner, m_get_permissions,
650                          m_getpwnam, users):
651        homes = []
652
653        root = '/tmp/root'
654        fpw = FakePwEnt(pw_name="root", pw_dir=root)
655        users["root"] = fpw
656
657        for name in names:
658            home = '/tmp/home/' + name
659            fpw = FakePwEnt(pw_name=name, pw_dir=home)
660            users[name] = fpw
661            homes.append(home)
662
663        m_get_permissions.side_effect = partial(
664            mock_get_permissions, mock_permissions)
665        m_get_owner.side_effect = partial(mock_get_owner, mock_permissions)
666        m_get_group.side_effect = partial(mock_get_group, mock_permissions)
667        m_getpwnam.side_effect = partial(mock_getpwnam, users)
668        return homes
669
670    def create_user_authorized_file(self, home, filename, content_key, keys):
671        user_ssh_folder = "%s/.ssh" % home
672        # /tmp/home/<user>/.ssh/authorized_keys = content_key
673        authorized_keys = self.tmp_path(filename, dir=user_ssh_folder)
674        util.write_file(authorized_keys, VALID_CONTENT[content_key])
675        keys[authorized_keys] = content_key
676        return authorized_keys
677
678    def create_global_authorized_file(self, filename, content_key, keys):
679        authorized_keys = self.tmp_path(filename, dir='/tmp')
680        util.write_file(authorized_keys, VALID_CONTENT[content_key])
681        keys[authorized_keys] = content_key
682        return authorized_keys
683
684    def create_sshd_config(self, authorized_keys_files):
685        sshd_config = self.tmp_path('sshd_config', dir="/tmp")
686        util.write_file(
687            sshd_config,
688            "AuthorizedKeysFile " + authorized_keys_files
689        )
690        return sshd_config
691
692    def execute_and_check(self, user, sshd_config, solution, keys,
693                          delete_keys=True):
694        (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys(
695            user, sshd_config)
696        content = ssh_util.update_authorized_keys(auth_key_entries, [])
697
698        self.assertEqual(auth_key_fn, solution)
699        for path, key in keys.items():
700            if path == solution:
701                self.assertTrue(VALID_CONTENT[key] in content)
702            else:
703                self.assertFalse(VALID_CONTENT[key] in content)
704
705        if delete_keys and os.path.isdir("/tmp/home/"):
706            util.delete_dir_contents("/tmp/home/")
707
708    @patch("cloudinit.ssh_util.pwd.getpwnam")
709    @patch("cloudinit.util.get_permissions")
710    @patch("cloudinit.util.get_owner")
711    @patch("cloudinit.util.get_group")
712    def test_single_user_two_local_files(
713        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
714    ):
715        user_bobby = 'bobby'
716        keys = {}
717        users = {}
718        mock_permissions = {
719            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
720            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
721            '/tmp/home/bobby/.ssh/user_keys': ('bobby', 'bobby', 0o600),
722            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
723        }
724
725        homes = self.create_fake_users(
726            [user_bobby], mock_permissions, m_get_group, m_get_owner,
727            m_get_permissions, m_getpwnam, users
728        )
729        home = homes[0]
730
731        # /tmp/home/bobby/.ssh/authorized_keys = rsa
732        authorized_keys = self.create_user_authorized_file(
733            home, 'authorized_keys', 'rsa', keys
734        )
735
736        # /tmp/home/bobby/.ssh/user_keys = dsa
737        user_keys = self.create_user_authorized_file(
738            home, 'user_keys', 'dsa', keys
739        )
740
741        # /tmp/sshd_config
742        options = "%s %s" % (authorized_keys, user_keys)
743        sshd_config = self.create_sshd_config(options)
744
745        self.execute_and_check(user_bobby, sshd_config, authorized_keys, keys)
746
747    @patch("cloudinit.ssh_util.pwd.getpwnam")
748    @patch("cloudinit.util.get_permissions")
749    @patch("cloudinit.util.get_owner")
750    @patch("cloudinit.util.get_group")
751    def test_single_user_two_local_files_inverted(
752        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
753    ):
754        user_bobby = 'bobby'
755        keys = {}
756        users = {}
757        mock_permissions = {
758            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
759            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
760            '/tmp/home/bobby/.ssh/user_keys': ('bobby', 'bobby', 0o600),
761            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
762        }
763
764        homes = self.create_fake_users(
765            [user_bobby], mock_permissions, m_get_group, m_get_owner,
766            m_get_permissions, m_getpwnam, users
767        )
768        home = homes[0]
769
770        # /tmp/home/bobby/.ssh/authorized_keys = rsa
771        authorized_keys = self.create_user_authorized_file(
772            home, 'authorized_keys', 'rsa', keys
773        )
774
775        # /tmp/home/bobby/.ssh/user_keys = dsa
776        user_keys = self.create_user_authorized_file(
777            home, 'user_keys', 'dsa', keys
778        )
779
780        # /tmp/sshd_config
781        options = "%s %s" % (user_keys, authorized_keys)
782        sshd_config = self.create_sshd_config(options)
783
784        self.execute_and_check(user_bobby, sshd_config, user_keys, keys)
785
786    @patch("cloudinit.ssh_util.pwd.getpwnam")
787    @patch("cloudinit.util.get_permissions")
788    @patch("cloudinit.util.get_owner")
789    @patch("cloudinit.util.get_group")
790    def test_single_user_local_global_files(
791        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
792    ):
793        user_bobby = 'bobby'
794        keys = {}
795        users = {}
796        mock_permissions = {
797            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
798            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
799            '/tmp/home/bobby/.ssh/user_keys': ('bobby', 'bobby', 0o600),
800            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
801        }
802
803        homes = self.create_fake_users(
804            [user_bobby], mock_permissions, m_get_group, m_get_owner,
805            m_get_permissions, m_getpwnam, users
806        )
807        home = homes[0]
808
809        # /tmp/home/bobby/.ssh/authorized_keys = rsa
810        authorized_keys = self.create_user_authorized_file(
811            home, 'authorized_keys', 'rsa', keys
812        )
813
814        # /tmp/home/bobby/.ssh/user_keys = dsa
815        user_keys = self.create_user_authorized_file(
816            home, 'user_keys', 'dsa', keys
817        )
818
819        authorized_keys_global = self.create_global_authorized_file(
820            'etc/ssh/authorized_keys', 'ecdsa', keys
821        )
822
823        options = "%s %s %s" % (authorized_keys_global, user_keys,
824                                authorized_keys)
825        sshd_config = self.create_sshd_config(options)
826
827        self.execute_and_check(user_bobby, sshd_config, user_keys, keys)
828
829    @patch("cloudinit.ssh_util.pwd.getpwnam")
830    @patch("cloudinit.util.get_permissions")
831    @patch("cloudinit.util.get_owner")
832    @patch("cloudinit.util.get_group")
833    def test_single_user_local_global_files_inverted(
834        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
835    ):
836        user_bobby = 'bobby'
837        keys = {}
838        users = {}
839        mock_permissions = {
840            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
841            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
842            '/tmp/home/bobby/.ssh/user_keys3': ('bobby', 'bobby', 0o600),
843            '/tmp/home/bobby/.ssh/authorized_keys2': ('bobby', 'bobby', 0o600),
844        }
845
846        homes = self.create_fake_users(
847            [user_bobby], mock_permissions, m_get_group, m_get_owner,
848            m_get_permissions, m_getpwnam, users
849        )
850        home = homes[0]
851
852        # /tmp/home/bobby/.ssh/authorized_keys = rsa
853        authorized_keys = self.create_user_authorized_file(
854            home, 'authorized_keys2', 'rsa', keys
855        )
856
857        # /tmp/home/bobby/.ssh/user_keys = dsa
858        user_keys = self.create_user_authorized_file(
859            home, 'user_keys3', 'dsa', keys
860        )
861
862        authorized_keys_global = self.create_global_authorized_file(
863            'etc/ssh/authorized_keys', 'ecdsa', keys
864        )
865
866        options = "%s %s %s" % (authorized_keys_global, authorized_keys,
867                                user_keys)
868        sshd_config = self.create_sshd_config(options)
869
870        self.execute_and_check(user_bobby, sshd_config, authorized_keys, keys)
871
872    @patch("cloudinit.ssh_util.pwd.getpwnam")
873    @patch("cloudinit.util.get_permissions")
874    @patch("cloudinit.util.get_owner")
875    @patch("cloudinit.util.get_group")
876    def test_single_user_global_file(
877        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
878    ):
879        user_bobby = 'bobby'
880        keys = {}
881        users = {}
882        mock_permissions = {
883            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
884            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
885            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
886        }
887
888        homes = self.create_fake_users(
889            [user_bobby], mock_permissions, m_get_group, m_get_owner,
890            m_get_permissions, m_getpwnam, users
891        )
892        home = homes[0]
893
894        # /tmp/etc/ssh/authorized_keys = rsa
895        authorized_keys_global = self.create_global_authorized_file(
896            'etc/ssh/authorized_keys', 'rsa', keys
897        )
898
899        options = "%s" % authorized_keys_global
900        sshd_config = self.create_sshd_config(options)
901
902        default = "%s/.ssh/authorized_keys" % home
903        self.execute_and_check(user_bobby, sshd_config, default, keys)
904
905    @patch("cloudinit.ssh_util.pwd.getpwnam")
906    @patch("cloudinit.util.get_permissions")
907    @patch("cloudinit.util.get_owner")
908    @patch("cloudinit.util.get_group")
909    def test_two_users_local_file_standard(
910        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
911    ):
912        keys = {}
913        users = {}
914        mock_permissions = {
915            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
916            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
917            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
918            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
919            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
920            '/tmp/home/suzie/.ssh/authorized_keys': ('suzie', 'suzie', 0o600),
921        }
922
923        user_bobby = 'bobby'
924        user_suzie = 'suzie'
925        homes = self.create_fake_users(
926            [user_bobby, user_suzie], mock_permissions, m_get_group,
927            m_get_owner, m_get_permissions, m_getpwnam, users
928        )
929        home_bobby = homes[0]
930        home_suzie = homes[1]
931
932        # /tmp/home/bobby/.ssh/authorized_keys = rsa
933        authorized_keys = self.create_user_authorized_file(
934            home_bobby, 'authorized_keys', 'rsa', keys
935        )
936
937        # /tmp/home/suzie/.ssh/authorized_keys = rsa
938        authorized_keys2 = self.create_user_authorized_file(
939            home_suzie, 'authorized_keys', 'ssh-xmss@openssh.com', keys
940        )
941
942        options = ".ssh/authorized_keys"
943        sshd_config = self.create_sshd_config(options)
944
945        self.execute_and_check(
946            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
947        )
948        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
949
950    @patch("cloudinit.ssh_util.pwd.getpwnam")
951    @patch("cloudinit.util.get_permissions")
952    @patch("cloudinit.util.get_owner")
953    @patch("cloudinit.util.get_group")
954    def test_two_users_local_file_custom(
955        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
956    ):
957        keys = {}
958        users = {}
959        mock_permissions = {
960            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
961            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
962            '/tmp/home/bobby/.ssh/authorized_keys2': ('bobby', 'bobby', 0o600),
963            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
964            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
965            '/tmp/home/suzie/.ssh/authorized_keys2': ('suzie', 'suzie', 0o600),
966        }
967
968        user_bobby = 'bobby'
969        user_suzie = 'suzie'
970        homes = self.create_fake_users(
971            [user_bobby, user_suzie], mock_permissions, m_get_group,
972            m_get_owner, m_get_permissions, m_getpwnam, users
973        )
974        home_bobby = homes[0]
975        home_suzie = homes[1]
976
977        # /tmp/home/bobby/.ssh/authorized_keys2 = rsa
978        authorized_keys = self.create_user_authorized_file(
979            home_bobby, 'authorized_keys2', 'rsa', keys
980        )
981
982        # /tmp/home/suzie/.ssh/authorized_keys2 = rsa
983        authorized_keys2 = self.create_user_authorized_file(
984            home_suzie, 'authorized_keys2', 'ssh-xmss@openssh.com', keys
985        )
986
987        options = ".ssh/authorized_keys2"
988        sshd_config = self.create_sshd_config(options)
989
990        self.execute_and_check(
991            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
992        )
993        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
994
995    @patch("cloudinit.ssh_util.pwd.getpwnam")
996    @patch("cloudinit.util.get_permissions")
997    @patch("cloudinit.util.get_owner")
998    @patch("cloudinit.util.get_group")
999    def test_two_users_local_global_files(
1000        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam
1001    ):
1002        keys = {}
1003        users = {}
1004        mock_permissions = {
1005            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
1006            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
1007            '/tmp/home/bobby/.ssh/authorized_keys2': ('bobby', 'bobby', 0o600),
1008            '/tmp/home/bobby/.ssh/user_keys3': ('bobby', 'bobby', 0o600),
1009            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
1010            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
1011            '/tmp/home/suzie/.ssh/authorized_keys2': ('suzie', 'suzie', 0o600),
1012            '/tmp/home/suzie/.ssh/user_keys3': ('suzie', 'suzie', 0o600),
1013        }
1014
1015        user_bobby = 'bobby'
1016        user_suzie = 'suzie'
1017        homes = self.create_fake_users(
1018            [user_bobby, user_suzie], mock_permissions, m_get_group,
1019            m_get_owner, m_get_permissions, m_getpwnam, users
1020        )
1021        home_bobby = homes[0]
1022        home_suzie = homes[1]
1023
1024        # /tmp/home/bobby/.ssh/authorized_keys2 = rsa
1025        self.create_user_authorized_file(
1026            home_bobby, 'authorized_keys2', 'rsa', keys
1027        )
1028        # /tmp/home/bobby/.ssh/user_keys3 = dsa
1029        user_keys = self.create_user_authorized_file(
1030            home_bobby, 'user_keys3', 'dsa', keys
1031        )
1032
1033        # /tmp/home/suzie/.ssh/authorized_keys2 = rsa
1034        authorized_keys2 = self.create_user_authorized_file(
1035            home_suzie, 'authorized_keys2', 'ssh-xmss@openssh.com', keys
1036        )
1037
1038        # /tmp/etc/ssh/authorized_keys = ecdsa
1039        authorized_keys_global = self.create_global_authorized_file(
1040            'etc/ssh/authorized_keys2', 'ecdsa', keys
1041        )
1042
1043        options = "%s %s %%h/.ssh/authorized_keys2" % \
1044            (authorized_keys_global, user_keys)
1045        sshd_config = self.create_sshd_config(options)
1046
1047        self.execute_and_check(
1048            user_bobby, sshd_config, user_keys, keys, delete_keys=False
1049        )
1050        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
1051
1052    @patch("cloudinit.util.get_user_groups")
1053    @patch("cloudinit.ssh_util.pwd.getpwnam")
1054    @patch("cloudinit.util.get_permissions")
1055    @patch("cloudinit.util.get_owner")
1056    @patch("cloudinit.util.get_group")
1057    def test_two_users_local_global_files_badguy(
1058        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
1059        m_get_user_groups
1060    ):
1061        keys = {}
1062        users = {}
1063        mock_permissions = {
1064            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
1065            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
1066            '/tmp/home/bobby/.ssh/authorized_keys2': ('bobby', 'bobby', 0o600),
1067            '/tmp/home/bobby/.ssh/user_keys3': ('bobby', 'bobby', 0o600),
1068            '/tmp/home/badguy': ('root', 'root', 0o755),
1069            '/tmp/home/badguy/home': ('root', 'root', 0o755),
1070            '/tmp/home/badguy/home/bobby': ('root', 'root', 0o655),
1071        }
1072
1073        user_bobby = 'bobby'
1074        user_badguy = 'badguy'
1075        home_bobby, *_ = self.create_fake_users(
1076            [user_bobby, user_badguy], mock_permissions, m_get_group,
1077            m_get_owner, m_get_permissions, m_getpwnam, users
1078        )
1079        m_get_user_groups.side_effect = mock_get_user_groups
1080
1081        # /tmp/home/bobby/.ssh/authorized_keys2 = rsa
1082        authorized_keys = self.create_user_authorized_file(
1083            home_bobby, 'authorized_keys2', 'rsa', keys
1084        )
1085        # /tmp/home/bobby/.ssh/user_keys3 = dsa
1086        user_keys = self.create_user_authorized_file(
1087            home_bobby, 'user_keys3', 'dsa', keys
1088        )
1089
1090        # /tmp/home/badguy/home/bobby = ""
1091        authorized_keys2 = self.tmp_path('home/bobby', dir="/tmp/home/badguy")
1092        util.write_file(authorized_keys2, '')
1093
1094        # /tmp/etc/ssh/authorized_keys = ecdsa
1095        authorized_keys_global = self.create_global_authorized_file(
1096            'etc/ssh/authorized_keys2', 'ecdsa', keys
1097        )
1098
1099        # /tmp/sshd_config
1100        options = "%s %%h/.ssh/authorized_keys2 %s %s" % \
1101            (authorized_keys2, authorized_keys_global, user_keys)
1102        sshd_config = self.create_sshd_config(options)
1103
1104        self.execute_and_check(
1105            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
1106        )
1107        self.execute_and_check(
1108            user_badguy, sshd_config, authorized_keys2, keys
1109        )
1110
1111    @patch("cloudinit.util.get_user_groups")
1112    @patch("cloudinit.ssh_util.pwd.getpwnam")
1113    @patch("cloudinit.util.get_permissions")
1114    @patch("cloudinit.util.get_owner")
1115    @patch("cloudinit.util.get_group")
1116    def test_two_users_unaccessible_file(
1117        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
1118        m_get_user_groups
1119    ):
1120        keys = {}
1121        users = {}
1122        mock_permissions = {
1123            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
1124            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
1125            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
1126
1127            '/tmp/etc': ('root', 'root', 0o755),
1128            '/tmp/etc/ssh': ('root', 'root', 0o755),
1129            '/tmp/etc/ssh/userkeys': ('root', 'root', 0o700),
1130            '/tmp/etc/ssh/userkeys/bobby': ('bobby', 'bobby', 0o600),
1131            '/tmp/etc/ssh/userkeys/badguy': ('badguy', 'badguy', 0o600),
1132
1133            '/tmp/home/badguy': ('badguy', 'badguy', 0o700),
1134            '/tmp/home/badguy/.ssh': ('badguy', 'badguy', 0o700),
1135            '/tmp/home/badguy/.ssh/authorized_keys':
1136                ('badguy', 'badguy', 0o600),
1137        }
1138
1139        user_bobby = 'bobby'
1140        user_badguy = 'badguy'
1141        homes = self.create_fake_users(
1142            [user_bobby, user_badguy], mock_permissions, m_get_group,
1143            m_get_owner, m_get_permissions, m_getpwnam, users
1144        )
1145        m_get_user_groups.side_effect = mock_get_user_groups
1146        home_bobby = homes[0]
1147        home_badguy = homes[1]
1148
1149        # /tmp/home/bobby/.ssh/authorized_keys = rsa
1150        authorized_keys = self.create_user_authorized_file(
1151            home_bobby, 'authorized_keys', 'rsa', keys
1152        )
1153        # /tmp/etc/ssh/userkeys/bobby = dsa
1154        # assume here that we can bypass userkeys, despite permissions
1155        self.create_global_authorized_file(
1156            'etc/ssh/userkeys/bobby', 'dsa', keys
1157        )
1158
1159        # /tmp/home/badguy/.ssh/authorized_keys = ssh-xmss@openssh.com
1160        authorized_keys2 = self.create_user_authorized_file(
1161            home_badguy, 'authorized_keys', 'ssh-xmss@openssh.com', keys
1162        )
1163
1164        # /tmp/etc/ssh/userkeys/badguy = ecdsa
1165        self.create_global_authorized_file(
1166            'etc/ssh/userkeys/badguy', 'ecdsa', keys
1167        )
1168
1169        # /tmp/sshd_config
1170        options = "/tmp/etc/ssh/userkeys/%u .ssh/authorized_keys"
1171        sshd_config = self.create_sshd_config(options)
1172
1173        self.execute_and_check(
1174            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
1175        )
1176        self.execute_and_check(
1177            user_badguy, sshd_config, authorized_keys2, keys
1178        )
1179
1180    @patch("cloudinit.util.get_user_groups")
1181    @patch("cloudinit.ssh_util.pwd.getpwnam")
1182    @patch("cloudinit.util.get_permissions")
1183    @patch("cloudinit.util.get_owner")
1184    @patch("cloudinit.util.get_group")
1185    def test_two_users_accessible_file(
1186        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
1187        m_get_user_groups
1188    ):
1189        keys = {}
1190        users = {}
1191        mock_permissions = {
1192            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
1193            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
1194            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
1195
1196            '/tmp/etc': ('root', 'root', 0o755),
1197            '/tmp/etc/ssh': ('root', 'root', 0o755),
1198            '/tmp/etc/ssh/userkeys': ('root', 'root', 0o755),
1199            '/tmp/etc/ssh/userkeys/bobby': ('bobby', 'bobby', 0o600),
1200            '/tmp/etc/ssh/userkeys/badguy': ('badguy', 'badguy', 0o600),
1201
1202            '/tmp/home/badguy': ('badguy', 'badguy', 0o700),
1203            '/tmp/home/badguy/.ssh': ('badguy', 'badguy', 0o700),
1204            '/tmp/home/badguy/.ssh/authorized_keys':
1205                ('badguy', 'badguy', 0o600),
1206        }
1207
1208        user_bobby = 'bobby'
1209        user_badguy = 'badguy'
1210        homes = self.create_fake_users(
1211            [user_bobby, user_badguy], mock_permissions, m_get_group,
1212            m_get_owner, m_get_permissions, m_getpwnam, users
1213        )
1214        m_get_user_groups.side_effect = mock_get_user_groups
1215        home_bobby = homes[0]
1216        home_badguy = homes[1]
1217
1218        # /tmp/home/bobby/.ssh/authorized_keys = rsa
1219        self.create_user_authorized_file(
1220            home_bobby, 'authorized_keys', 'rsa', keys
1221        )
1222        # /tmp/etc/ssh/userkeys/bobby = dsa
1223        # assume here that we can bypass userkeys, despite permissions
1224        authorized_keys = self.create_global_authorized_file(
1225            'etc/ssh/userkeys/bobby', 'dsa', keys
1226        )
1227
1228        # /tmp/home/badguy/.ssh/authorized_keys = ssh-xmss@openssh.com
1229        self.create_user_authorized_file(
1230            home_badguy, 'authorized_keys', 'ssh-xmss@openssh.com', keys
1231        )
1232
1233        # /tmp/etc/ssh/userkeys/badguy = ecdsa
1234        authorized_keys2 = self.create_global_authorized_file(
1235            'etc/ssh/userkeys/badguy', 'ecdsa', keys
1236        )
1237
1238        # /tmp/sshd_config
1239        options = "/tmp/etc/ssh/userkeys/%u .ssh/authorized_keys"
1240        sshd_config = self.create_sshd_config(options)
1241
1242        self.execute_and_check(
1243            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
1244        )
1245        self.execute_and_check(
1246            user_badguy, sshd_config, authorized_keys2, keys
1247        )
1248
1249    @patch("cloudinit.util.get_user_groups")
1250    @patch("cloudinit.ssh_util.pwd.getpwnam")
1251    @patch("cloudinit.util.get_permissions")
1252    @patch("cloudinit.util.get_owner")
1253    @patch("cloudinit.util.get_group")
1254    def test_two_users_hardcoded_single_user_file(
1255        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
1256        m_get_user_groups
1257    ):
1258        keys = {}
1259        users = {}
1260        mock_permissions = {
1261            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
1262            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
1263            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
1264
1265            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
1266            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
1267            '/tmp/home/suzie/.ssh/authorized_keys': ('suzie', 'suzie', 0o600),
1268        }
1269
1270        user_bobby = 'bobby'
1271        user_suzie = 'suzie'
1272        homes = self.create_fake_users(
1273            [user_bobby, user_suzie], mock_permissions, m_get_group,
1274            m_get_owner, m_get_permissions, m_getpwnam, users
1275        )
1276        home_bobby = homes[0]
1277        home_suzie = homes[1]
1278        m_get_user_groups.side_effect = mock_get_user_groups
1279
1280        # /tmp/home/bobby/.ssh/authorized_keys = rsa
1281        authorized_keys = self.create_user_authorized_file(
1282            home_bobby, 'authorized_keys', 'rsa', keys
1283        )
1284
1285        # /tmp/home/suzie/.ssh/authorized_keys = ssh-xmss@openssh.com
1286        self.create_user_authorized_file(
1287            home_suzie, 'authorized_keys', 'ssh-xmss@openssh.com', keys
1288        )
1289
1290        # /tmp/sshd_config
1291        options = "%s" % (authorized_keys)
1292        sshd_config = self.create_sshd_config(options)
1293
1294        self.execute_and_check(
1295            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
1296        )
1297        default = "%s/.ssh/authorized_keys" % home_suzie
1298        self.execute_and_check(user_suzie, sshd_config, default, keys)
1299
1300    @patch("cloudinit.util.get_user_groups")
1301    @patch("cloudinit.ssh_util.pwd.getpwnam")
1302    @patch("cloudinit.util.get_permissions")
1303    @patch("cloudinit.util.get_owner")
1304    @patch("cloudinit.util.get_group")
1305    def test_two_users_hardcoded_single_user_file_inverted(
1306        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
1307        m_get_user_groups
1308    ):
1309        keys = {}
1310        users = {}
1311        mock_permissions = {
1312            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
1313            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
1314            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
1315
1316            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
1317            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
1318            '/tmp/home/suzie/.ssh/authorized_keys': ('suzie', 'suzie', 0o600),
1319        }
1320
1321        user_bobby = 'bobby'
1322        user_suzie = 'suzie'
1323        homes = self.create_fake_users(
1324            [user_bobby, user_suzie], mock_permissions, m_get_group,
1325            m_get_owner, m_get_permissions, m_getpwnam, users
1326        )
1327        home_bobby = homes[0]
1328        home_suzie = homes[1]
1329        m_get_user_groups.side_effect = mock_get_user_groups
1330
1331        # /tmp/home/bobby/.ssh/authorized_keys = rsa
1332        self.create_user_authorized_file(
1333            home_bobby, 'authorized_keys', 'rsa', keys
1334        )
1335
1336        # /tmp/home/suzie/.ssh/authorized_keys = ssh-xmss@openssh.com
1337        authorized_keys2 = self.create_user_authorized_file(
1338            home_suzie, 'authorized_keys', 'ssh-xmss@openssh.com', keys
1339        )
1340
1341        # /tmp/sshd_config
1342        options = "%s" % (authorized_keys2)
1343        sshd_config = self.create_sshd_config(options)
1344
1345        default = "%s/.ssh/authorized_keys" % home_bobby
1346        self.execute_and_check(
1347            user_bobby, sshd_config, default, keys, delete_keys=False
1348        )
1349        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
1350
1351    @patch("cloudinit.util.get_user_groups")
1352    @patch("cloudinit.ssh_util.pwd.getpwnam")
1353    @patch("cloudinit.util.get_permissions")
1354    @patch("cloudinit.util.get_owner")
1355    @patch("cloudinit.util.get_group")
1356    def test_two_users_hardcoded_user_files(
1357        self, m_get_group, m_get_owner, m_get_permissions, m_getpwnam,
1358        m_get_user_groups
1359    ):
1360        keys = {}
1361        users = {}
1362        mock_permissions = {
1363            '/tmp/home/bobby': ('bobby', 'bobby', 0o700),
1364            '/tmp/home/bobby/.ssh': ('bobby', 'bobby', 0o700),
1365            '/tmp/home/bobby/.ssh/authorized_keys': ('bobby', 'bobby', 0o600),
1366
1367            '/tmp/home/suzie': ('suzie', 'suzie', 0o700),
1368            '/tmp/home/suzie/.ssh': ('suzie', 'suzie', 0o700),
1369            '/tmp/home/suzie/.ssh/authorized_keys': ('suzie', 'suzie', 0o600),
1370        }
1371
1372        user_bobby = 'bobby'
1373        user_suzie = 'suzie'
1374        homes = self.create_fake_users(
1375            [user_bobby, user_suzie], mock_permissions, m_get_group,
1376            m_get_owner, m_get_permissions, m_getpwnam, users
1377        )
1378        home_bobby = homes[0]
1379        home_suzie = homes[1]
1380        m_get_user_groups.side_effect = mock_get_user_groups
1381
1382        # /tmp/home/bobby/.ssh/authorized_keys = rsa
1383        authorized_keys = self.create_user_authorized_file(
1384            home_bobby, 'authorized_keys', 'rsa', keys
1385        )
1386
1387        # /tmp/home/suzie/.ssh/authorized_keys = ssh-xmss@openssh.com
1388        authorized_keys2 = self.create_user_authorized_file(
1389            home_suzie, 'authorized_keys', 'ssh-xmss@openssh.com', keys
1390        )
1391
1392        # /tmp/etc/ssh/authorized_keys = ecdsa
1393        authorized_keys_global = self.create_global_authorized_file(
1394            'etc/ssh/authorized_keys', 'ecdsa', keys
1395        )
1396
1397        # /tmp/sshd_config
1398        options = "%s %s %s" % \
1399            (authorized_keys_global, authorized_keys, authorized_keys2)
1400        sshd_config = self.create_sshd_config(options)
1401
1402        self.execute_and_check(
1403            user_bobby, sshd_config, authorized_keys, keys, delete_keys=False
1404        )
1405        self.execute_and_check(user_suzie, sshd_config, authorized_keys2, keys)
1406
1407# vi: ts=4 expandtab
1408