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