From 102c985a65d2e6328c39769375a9bd1a31145c3e Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Thu, 14 Dec 2023 20:53:38 +0000 Subject: [PATCH] Automatically install SSH host keys for public regions --- composer.json | 3 +- config-defaults.yaml | 5 ++ resources/ssh/host-keys | 30 ++++++++ scripts/update-known-hosts.php | 84 ++++++++++++++++++++++ src/Command/CommandBase.php | 12 ++-- src/Command/SshCert/SshCertLoadCommand.php | 1 + src/Service/Ssh.php | 19 +++-- src/Service/SshConfig.php | 43 ++++++++++- 8 files changed, 182 insertions(+), 15 deletions(-) create mode 100644 resources/ssh/host-keys create mode 100755 scripts/update-known-hosts.php diff --git a/composer.json b/composer.json index c0da283ceb..2e0b5f0520 100644 --- a/composer.json +++ b/composer.json @@ -66,6 +66,7 @@ } }, "scripts": { - "update-countries": "php scripts/update-countries.php" + "update-countries": "php scripts/update-countries.php", + "update-known-hosts": "php scripts/update-known-hosts.php" } } diff --git a/config-defaults.yaml b/config-defaults.yaml index 872bbfe12e..58b75b8633 100644 --- a/config-defaults.yaml +++ b/config-defaults.yaml @@ -292,6 +292,11 @@ api: # The timeout in seconds for a "git push" command. Set to null for no timeout. git_push_timeout: 3600 + # A file containing SSH host keys to install for the user. + # If a relative path is given, it is considered relative to the CLI source + # code root. Note this will need to be built into the Phar. + ssh_host_keys_file: resources/ssh/host-keys + # How the CLI detects and configures Git repositories as projects. detection: ## Required keys that must be defined elsewhere: diff --git a/resources/ssh/host-keys b/resources/ssh/host-keys new file mode 100644 index 0000000000..38d4568ab5 --- /dev/null +++ b/resources/ssh/host-keys @@ -0,0 +1,30 @@ +git.au-2.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3GhkAUQ/+wx9oBekSKqCIQZqKBh4MaqrdguK0poqwEBMzbKGo5WtQSwN2nx0RWDNTJFJ0ipaC9XszJBc53O+cNncvj9OyFNh9zBclxRFt1bgAs+upORs5GXAaA2Xzfx6VCfBLG/uN65etZLV+klP9yAqgyRjOQqJG8IUejeuGb+TA+aXBupm1SHIffqQm/TMLqdfKn5Uz9MkVXYfkVMM2oQyVRvAKsqS9DiZs/kkubBhetaAt03lpjXm69jm9rCusw/GTODwd3LESzPe9fl38wkVbTJ9M9ia+5bxZMbF0oScNeV0CpQ0VU34dv9prGSlekVM7i6Ww06Cn9QEtTIhX +git.au.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNswNEfbW/j3/mIuMVU10lWFSxYDcoqUPaAWqsr76oMsHkRRrwGIbN3ckDbq7rga2AXi2eYgPrukZLODlRuJb91aE2BWBYnQ4pe1JQ0vjdEbZ5QiBqmJu7dU/OdGwYce0DEnDVR3LSPBfvIzXAdqzwW5tJsEAlEoaF+s8VhcjSCCZoqEtovfJCdvPoG9oz03GYX3/2yMJM8OJ/VCBnivxFJKfTMGAkPzNEmOhcuK+knhJLW/Tmxh36rQTh3uuxZNrNs3Ol3jzl/h95Qpd+pDyx4LOReno+tOjIpSm8ets7AjW6XetiGpqjBuVPoAzwN9ESzogrexP7JmQ2KI5asxjt +git.ca-1.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGJxvHwSRPQVF0b2rFcmJ4e9GSDZVpdDs61kSpXrX2qnzK9dcRa8xwsa28z2uzzZfBXLrJyLCDwWd0iv2VpbZ5Bh9EY9Hxf38PzUzaSzP7kasiVN02UeDCE3X7jYyxkT1wvDbnafl31Hurra5KH8Oo06ThxJvj3hPS1xkTXuoquEx5AKWo4yLgoEEWePxwQjrsCcRJbiel0P9gZh4mohSK7Bku/UnmU/wTa1G+IjecBej2YHwjdfBCF9iRtMcofHy0S/YNuyAzNrJVMGayEh0eq6QKuS5rWntExxSvj8dU3/TBW+zv88lRhvueN44orUjMO2mfQoMbtOkbHH4yZ1LT +git.de-2.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy8x0JUpNurMQZzxyOKy3PSlly5SIUuqgZHUoQ7UlD/dLdienrs5gLGz3+N3asrX6O1AB8zef3VN6YiQ2VNXXlMu9WNV1mDUfXVxq5S8/mwWkdImK67vwFrLPU5tlZOVt2dB01LqZSLrWhKSRVw3bTD/Q2MiSesBIKFFWfRvTRZOpRrg/xF44KnA50QXwpvm34DTdQf6UwuywKPu6quBfYUUd6RYbdeTwmSSn/d3UCdrS9bi8kGPzwRSZ6OXyQNihETZrw095XPVXOl/MbC4mPjyqj4rkcVbevAPkhnQqVI/XJJAVdaDzRbzRP0U2o/eoAW37BIQya+QC/5jgi2R0N +git.eu-2.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFKM3dQWu2/aCXDhZVSSHEQskK+6pN+snPS7XSfd9TgbmhUKYhp22bHsA/YAiUh/V1CB/hsGQLByvPNt/J0lRAn8in6VoCOad6NRZErs315FsmiU2jH+bcS4imKj3ayje81cn9qJPJpe5Jg6YIqz00l/XjUiz58ehFJE+xkqiKmZ46uVKH9z6jC46WQj5ia4tZYGpF5l0fAzfzq7IXhz8PLGfrDYJt5O28I1CerJ8tCX2zdOJ9I/urNuZ/FlnijDtZq3LP6/lmWOXoxOFaDKHhCwaRnlgonMskIZgnh5wOxi5QqLpZ+D0KX3x8iDVO1bstf/HKrOPSen0i8QQyj+Bd +git.eu-4.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKIClyq/OHY6u+tlG+LXVwdcMsOGNG1UTOYuYrdCx4CBPiHbdZkizhFFDiJD4pTTtISW7zuiKGWiYmqznwDfeZ2jfEH4NjCyhsMqFWgJ/MwIQDZJGZAGMj9QXEJpCIVyb4D7n1VuKlt0W1KLL4C/a0E7tTU9+1bF38lyhO/QvRNNJD1fgYeZse75stVqftNmp6HA2JIvcQolH1afMSyi0c0CgCPL97nC+qYBOCRd+vtGP/5q5GT9KHENlIcmciu7r23NtIo5U2ytqnKtgnDkngpLTWgPXlepr+VIBSfy1XnoC+gW8uJ6TtF6+L1yz4NrsvBqTJfcdG6318xQ7Hnwz5 +git.eu-5.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHVvr73G2u1j0s0RqixwpIGsTKagv3vMErXj/ixorbbuuNFmln8l2OqLo9cvg6QmO8OJFpY1xdOAIQ1hfzcZud+LCdMAxvsC7lz9VMb26Vchuc7o+lq0+w8ORAlKqlFfl2MpLtbEuuRaellXaTlQ36cSKwuCHOfzqsKDyb+Umpm+bYk6ICLnJyiEldo1cs8cAQNIgXRmPiJ2Ws/OamfJi/b1CajNZr6Q3gs7oqRbWBt9mgDFU9sFDMWDmmBAvDQj2azSk2i1hFQ4/lzczt6vQH9vusnESPCJfAiMJb60868BhuvOV9oxITItzwgNDFJltL8LNadZ4x6VvMmPV0DpJb +git.eu.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDo0sNDnrxZ4N0UEFhdgYmX5htjsY/XnG4aJImgCaqGhO6/ijj5kK+MLkHXGsekVWWIe69egMsXFII5Kj354Os4R7615CJfcxoq46W/kIkDwI2wAIh1dtJ/hfA0woE5T3ydETY+SflE0KwgPVesy8jznL8TuJ4/YF4nSgKg9RtPqxDz+7VnjEPMHilGVGqR1qHYDve8Y3jQDZW+1X6vCIhWrxmgQ0h0aq8oaA/Em2zAi7OQZVym02tnibnSU13AcRCHZ3hmecZuoqRxFUssUs+tmlHGZ1Fh7GQ8yXqjeFLevjEcIPSjo4zYG9Eg34tOeRSGj9wp88w+Miw70rxw0mwz +git.fr-3.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0ipGEbPJDcd2lHcdzlEf5f7Si6HMl0i+Pj5g9tbbDNL2DwVrNBmfhGp26mYemWSHbj1hH0qn2vEZaXLG46uPcR4Nu126vNZ/MLBnuh13K3yCxE7wyjAT2B7aNgCKsIiaQpsT1ciZz42Qes1DRqA2r4PIsV97EAP6/k7atPF7wcO2A4CaVzP6na/CyswS5Gq2N2fUU6xOKcOGtAnNHEFptz4CBdbv7seo1Ac7QbN0Ks9+Vq+QaEPYymR8WdHa1HIDh5ZrHc8DTXatuUz1c1E0u5gnFpLsNkuJ5/ndv1lgTgNDcn2STCEaKvvRKgjGiQ0Chrdj3VVTt4Gt77qgvPb4N +git.fr-4.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvCpjQnnCR6VDXTL/neY5wqIe7Z1OUSxdLMqDCGpkDSLTN7I9Jj8MvMR1A0nuehwRpV1kHFJ4HryPyFGvR9BSFib4VZ1cd51iTSbphDkpbp3E7ukNzqV445+iPEwRPNDzJFKL3z/C7RR9ss6crP5h1Cn/qQaxjVTZry+zmv0IgSIkb3ipCN6QL6nXUOsabk4YB4YiVUlQWSNizWNn5cFOvby7DCd/lzlOz2zR7fgD2Cjbzs+2uz832r2wb2CnsVMAdGOs/MnlPy7RS16h6wa8M10UxJztTESdJ8zj1qbToPQyKHILpSSX3+E2wrrdVJ3OeG2skwElp/EhHAjcv7C65 +git.uk-1.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+zGy0b99QbRYBuHtym0jEMMZXo/E8WYYcp9ua7I7p2KOzBKoze7udHMB2tRVC4RTrSPeiMoN59Qjl8JgN5/qVW7TUOyotcftYVNm0YUpv8gWlXxnnJxxHghkWrOVBEl773t0att6da2LKsoJ2hWR9ICivcX1f1cnhLyBl4SHTHnlyl6Pp+ZrB2n/KD5z9duQTZ9d6OdUaJ9GJ181HAypH7+CWq6NHejDdX/sMQrIkpe79fx5wrhUMUu/UxDcmupBwn3Xg01BoHvxz+GRg/hbEH+J/6IYH/ZyMqUbo+iav7xILw76Q0dA/nbMWVmhfucAC+UVI0dehH+KI2qqsqMFJ +git.us-2.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDM1ZokfNnDwh/eoLazPQ9X0A4y2Ypht9DgKu3P7Rh1R7GvXInb8aDmGhJV0ynmGFPtmI109IdltFUfY9q91lwPxu0+8PFxcZjziWAgk+/qXJ4XO/vj0fa3ziwA4uQU2WGRoBjrJ9aCor71pdOnMzNAc0vTuBI/GRbxPwYJkfrNBBp9fNKhddrEujKSYylobhClAwA8PUK74ptCnk1G9utd0WBeQ6Oe4f24Okv1qm3htZ54Prfpo6l82xNCR+1b5crP3fzYlqSvwC5OKthKOgBYvxVwi/FfT+hGrus3EQbezUEh3KevnEq0a9iCpmNuVehSsi3ikfPJql2sNBPkRwmN +git.us-3.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLFxThoz62qBwaVmuJxG2jC+t+7Z44IvX7fYOl3cYoo/CNrvQuXnr+T6HyPtxOX75k8gPlKfrGVclEK3v0RoPX9BgQqHeS4d//lIqnhARccaWJVnjwc2f8fK/QKhDE45pmL/jSnMyiwDp32aOKt8CpZUqBI94lBhbsKcl+ud4U8zJ8nyV2CgE5PYB3GGpSxARM3fckhfrOevkAaTGtNBNE9myVVevuA5cgx6nQtovE5ewuWUML1XTWqhCtFyrweX2naqQ99gTZj1u5eg+WgttsGbiMw8SJ1942A04+Jq7vgNuJDnTV7EKiguS5QVZPmgiQEsiOrRWKvF9IniG3Bsp5 +git.us-4.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDORGcfHsCq33DvSQjoElzb3KzD9FU/epQ5c0DuOUJzpMx4fOERSxifyfn6GKcl59hEqq4FErttq+hqWyzuifWhpAFWSC4oLveXpkOXUFqkT31hUt7h6VLTSQfOOgp5d3exYuU3XQAuJDFYtVtO+EwMK37exC+1VUrDnXO1N7C3wU+KCfqRvudnXG8jnOQWY5GAeyLa08oJgwiFsgKKrNbulmEzOCL0N7/TAX0enLV5SLsD98Y1a7ARm1fqM1LuiMMExneD5TuRU+5J33kkCMlf/6JvYm52ppacM0+f1QDAbuKjqAM68ntRHAeQuL42Y9LWDbdYl9Slq3Z7tqzmy23n +git.us.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTrolLNSsqP9rtd7NDiH0D7mwIuSJYBx3HxLv/84PFrEPJE0nXlbYpa0dMdo38ZFUaJw05nXfh7y1OSuKHw1ohfsmUT6cA+F13fbMyXBRKG69TNuHBt3cvSQ3HngSyZ3P9TXj7UyuCK2eDYiiUrsrBmZM7WQD/lsE63/PufOCQk3BwIS/xT+ZNiaXHRZBHR70cQcn8Nn3FfDrtf/WRtcytgm/8A8rd4YWyJsmhDUlS7j/8oP7DcB0yPNq1QGuEJPF0zQGiXd/ySbaaZPvEyMbW+mvox7rxgUViIqGw3s5ys1nFCmd/SYCJrfC1i+q4z13n7RkfMBaNBc0eufT2WHeJ +ssh.au-2.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3GhkAUQ/+wx9oBekSKqCIQZqKBh4MaqrdguK0poqwEBMzbKGo5WtQSwN2nx0RWDNTJFJ0ipaC9XszJBc53O+cNncvj9OyFNh9zBclxRFt1bgAs+upORs5GXAaA2Xzfx6VCfBLG/uN65etZLV+klP9yAqgyRjOQqJG8IUejeuGb+TA+aXBupm1SHIffqQm/TMLqdfKn5Uz9MkVXYfkVMM2oQyVRvAKsqS9DiZs/kkubBhetaAt03lpjXm69jm9rCusw/GTODwd3LESzPe9fl38wkVbTJ9M9ia+5bxZMbF0oScNeV0CpQ0VU34dv9prGSlekVM7i6Ww06Cn9QEtTIhX +ssh.au.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNswNEfbW/j3/mIuMVU10lWFSxYDcoqUPaAWqsr76oMsHkRRrwGIbN3ckDbq7rga2AXi2eYgPrukZLODlRuJb91aE2BWBYnQ4pe1JQ0vjdEbZ5QiBqmJu7dU/OdGwYce0DEnDVR3LSPBfvIzXAdqzwW5tJsEAlEoaF+s8VhcjSCCZoqEtovfJCdvPoG9oz03GYX3/2yMJM8OJ/VCBnivxFJKfTMGAkPzNEmOhcuK+knhJLW/Tmxh36rQTh3uuxZNrNs3Ol3jzl/h95Qpd+pDyx4LOReno+tOjIpSm8ets7AjW6XetiGpqjBuVPoAzwN9ESzogrexP7JmQ2KI5asxjt +ssh.ca-1.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGJxvHwSRPQVF0b2rFcmJ4e9GSDZVpdDs61kSpXrX2qnzK9dcRa8xwsa28z2uzzZfBXLrJyLCDwWd0iv2VpbZ5Bh9EY9Hxf38PzUzaSzP7kasiVN02UeDCE3X7jYyxkT1wvDbnafl31Hurra5KH8Oo06ThxJvj3hPS1xkTXuoquEx5AKWo4yLgoEEWePxwQjrsCcRJbiel0P9gZh4mohSK7Bku/UnmU/wTa1G+IjecBej2YHwjdfBCF9iRtMcofHy0S/YNuyAzNrJVMGayEh0eq6QKuS5rWntExxSvj8dU3/TBW+zv88lRhvueN44orUjMO2mfQoMbtOkbHH4yZ1LT +ssh.de-2.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy8x0JUpNurMQZzxyOKy3PSlly5SIUuqgZHUoQ7UlD/dLdienrs5gLGz3+N3asrX6O1AB8zef3VN6YiQ2VNXXlMu9WNV1mDUfXVxq5S8/mwWkdImK67vwFrLPU5tlZOVt2dB01LqZSLrWhKSRVw3bTD/Q2MiSesBIKFFWfRvTRZOpRrg/xF44KnA50QXwpvm34DTdQf6UwuywKPu6quBfYUUd6RYbdeTwmSSn/d3UCdrS9bi8kGPzwRSZ6OXyQNihETZrw095XPVXOl/MbC4mPjyqj4rkcVbevAPkhnQqVI/XJJAVdaDzRbzRP0U2o/eoAW37BIQya+QC/5jgi2R0N +ssh.eu-2.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFKM3dQWu2/aCXDhZVSSHEQskK+6pN+snPS7XSfd9TgbmhUKYhp22bHsA/YAiUh/V1CB/hsGQLByvPNt/J0lRAn8in6VoCOad6NRZErs315FsmiU2jH+bcS4imKj3ayje81cn9qJPJpe5Jg6YIqz00l/XjUiz58ehFJE+xkqiKmZ46uVKH9z6jC46WQj5ia4tZYGpF5l0fAzfzq7IXhz8PLGfrDYJt5O28I1CerJ8tCX2zdOJ9I/urNuZ/FlnijDtZq3LP6/lmWOXoxOFaDKHhCwaRnlgonMskIZgnh5wOxi5QqLpZ+D0KX3x8iDVO1bstf/HKrOPSen0i8QQyj+Bd +ssh.eu-4.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKIClyq/OHY6u+tlG+LXVwdcMsOGNG1UTOYuYrdCx4CBPiHbdZkizhFFDiJD4pTTtISW7zuiKGWiYmqznwDfeZ2jfEH4NjCyhsMqFWgJ/MwIQDZJGZAGMj9QXEJpCIVyb4D7n1VuKlt0W1KLL4C/a0E7tTU9+1bF38lyhO/QvRNNJD1fgYeZse75stVqftNmp6HA2JIvcQolH1afMSyi0c0CgCPL97nC+qYBOCRd+vtGP/5q5GT9KHENlIcmciu7r23NtIo5U2ytqnKtgnDkngpLTWgPXlepr+VIBSfy1XnoC+gW8uJ6TtF6+L1yz4NrsvBqTJfcdG6318xQ7Hnwz5 +ssh.eu-5.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHVvr73G2u1j0s0RqixwpIGsTKagv3vMErXj/ixorbbuuNFmln8l2OqLo9cvg6QmO8OJFpY1xdOAIQ1hfzcZud+LCdMAxvsC7lz9VMb26Vchuc7o+lq0+w8ORAlKqlFfl2MpLtbEuuRaellXaTlQ36cSKwuCHOfzqsKDyb+Umpm+bYk6ICLnJyiEldo1cs8cAQNIgXRmPiJ2Ws/OamfJi/b1CajNZr6Q3gs7oqRbWBt9mgDFU9sFDMWDmmBAvDQj2azSk2i1hFQ4/lzczt6vQH9vusnESPCJfAiMJb60868BhuvOV9oxITItzwgNDFJltL8LNadZ4x6VvMmPV0DpJb +ssh.eu.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDo0sNDnrxZ4N0UEFhdgYmX5htjsY/XnG4aJImgCaqGhO6/ijj5kK+MLkHXGsekVWWIe69egMsXFII5Kj354Os4R7615CJfcxoq46W/kIkDwI2wAIh1dtJ/hfA0woE5T3ydETY+SflE0KwgPVesy8jznL8TuJ4/YF4nSgKg9RtPqxDz+7VnjEPMHilGVGqR1qHYDve8Y3jQDZW+1X6vCIhWrxmgQ0h0aq8oaA/Em2zAi7OQZVym02tnibnSU13AcRCHZ3hmecZuoqRxFUssUs+tmlHGZ1Fh7GQ8yXqjeFLevjEcIPSjo4zYG9Eg34tOeRSGj9wp88w+Miw70rxw0mwz +ssh.fr-3.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0ipGEbPJDcd2lHcdzlEf5f7Si6HMl0i+Pj5g9tbbDNL2DwVrNBmfhGp26mYemWSHbj1hH0qn2vEZaXLG46uPcR4Nu126vNZ/MLBnuh13K3yCxE7wyjAT2B7aNgCKsIiaQpsT1ciZz42Qes1DRqA2r4PIsV97EAP6/k7atPF7wcO2A4CaVzP6na/CyswS5Gq2N2fUU6xOKcOGtAnNHEFptz4CBdbv7seo1Ac7QbN0Ks9+Vq+QaEPYymR8WdHa1HIDh5ZrHc8DTXatuUz1c1E0u5gnFpLsNkuJ5/ndv1lgTgNDcn2STCEaKvvRKgjGiQ0Chrdj3VVTt4Gt77qgvPb4N +ssh.fr-4.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvCpjQnnCR6VDXTL/neY5wqIe7Z1OUSxdLMqDCGpkDSLTN7I9Jj8MvMR1A0nuehwRpV1kHFJ4HryPyFGvR9BSFib4VZ1cd51iTSbphDkpbp3E7ukNzqV445+iPEwRPNDzJFKL3z/C7RR9ss6crP5h1Cn/qQaxjVTZry+zmv0IgSIkb3ipCN6QL6nXUOsabk4YB4YiVUlQWSNizWNn5cFOvby7DCd/lzlOz2zR7fgD2Cjbzs+2uz832r2wb2CnsVMAdGOs/MnlPy7RS16h6wa8M10UxJztTESdJ8zj1qbToPQyKHILpSSX3+E2wrrdVJ3OeG2skwElp/EhHAjcv7C65 +ssh.uk-1.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+zGy0b99QbRYBuHtym0jEMMZXo/E8WYYcp9ua7I7p2KOzBKoze7udHMB2tRVC4RTrSPeiMoN59Qjl8JgN5/qVW7TUOyotcftYVNm0YUpv8gWlXxnnJxxHghkWrOVBEl773t0att6da2LKsoJ2hWR9ICivcX1f1cnhLyBl4SHTHnlyl6Pp+ZrB2n/KD5z9duQTZ9d6OdUaJ9GJ181HAypH7+CWq6NHejDdX/sMQrIkpe79fx5wrhUMUu/UxDcmupBwn3Xg01BoHvxz+GRg/hbEH+J/6IYH/ZyMqUbo+iav7xILw76Q0dA/nbMWVmhfucAC+UVI0dehH+KI2qqsqMFJ +ssh.us-2.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDM1ZokfNnDwh/eoLazPQ9X0A4y2Ypht9DgKu3P7Rh1R7GvXInb8aDmGhJV0ynmGFPtmI109IdltFUfY9q91lwPxu0+8PFxcZjziWAgk+/qXJ4XO/vj0fa3ziwA4uQU2WGRoBjrJ9aCor71pdOnMzNAc0vTuBI/GRbxPwYJkfrNBBp9fNKhddrEujKSYylobhClAwA8PUK74ptCnk1G9utd0WBeQ6Oe4f24Okv1qm3htZ54Prfpo6l82xNCR+1b5crP3fzYlqSvwC5OKthKOgBYvxVwi/FfT+hGrus3EQbezUEh3KevnEq0a9iCpmNuVehSsi3ikfPJql2sNBPkRwmN +ssh.us-3.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLFxThoz62qBwaVmuJxG2jC+t+7Z44IvX7fYOl3cYoo/CNrvQuXnr+T6HyPtxOX75k8gPlKfrGVclEK3v0RoPX9BgQqHeS4d//lIqnhARccaWJVnjwc2f8fK/QKhDE45pmL/jSnMyiwDp32aOKt8CpZUqBI94lBhbsKcl+ud4U8zJ8nyV2CgE5PYB3GGpSxARM3fckhfrOevkAaTGtNBNE9myVVevuA5cgx6nQtovE5ewuWUML1XTWqhCtFyrweX2naqQ99gTZj1u5eg+WgttsGbiMw8SJ1942A04+Jq7vgNuJDnTV7EKiguS5QVZPmgiQEsiOrRWKvF9IniG3Bsp5 +ssh.us-4.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDORGcfHsCq33DvSQjoElzb3KzD9FU/epQ5c0DuOUJzpMx4fOERSxifyfn6GKcl59hEqq4FErttq+hqWyzuifWhpAFWSC4oLveXpkOXUFqkT31hUt7h6VLTSQfOOgp5d3exYuU3XQAuJDFYtVtO+EwMK37exC+1VUrDnXO1N7C3wU+KCfqRvudnXG8jnOQWY5GAeyLa08oJgwiFsgKKrNbulmEzOCL0N7/TAX0enLV5SLsD98Y1a7ARm1fqM1LuiMMExneD5TuRU+5J33kkCMlf/6JvYm52ppacM0+f1QDAbuKjqAM68ntRHAeQuL42Y9LWDbdYl9Slq3Z7tqzmy23n +ssh.us.platform.sh ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTrolLNSsqP9rtd7NDiH0D7mwIuSJYBx3HxLv/84PFrEPJE0nXlbYpa0dMdo38ZFUaJw05nXfh7y1OSuKHw1ohfsmUT6cA+F13fbMyXBRKG69TNuHBt3cvSQ3HngSyZ3P9TXj7UyuCK2eDYiiUrsrBmZM7WQD/lsE63/PufOCQk3BwIS/xT+ZNiaXHRZBHR70cQcn8Nn3FfDrtf/WRtcytgm/8A8rd4YWyJsmhDUlS7j/8oP7DcB0yPNq1QGuEJPF0zQGiXd/ySbaaZPvEyMbW+mvox7rxgUViIqGw3s5ys1nFCmd/SYCJrfC1i+q4z13n7RkfMBaNBc0eufT2WHeJ diff --git a/scripts/update-known-hosts.php b/scripts/update-known-hosts.php new file mode 100755 index 0000000000..53f53a3348 --- /dev/null +++ b/scripts/update-known-hosts.php @@ -0,0 +1,84 @@ +#!/usr/bin/env php +getErrorOutput(); + +if ($config->has('api.ssh_host_keys')) { + $stdErr->writeln('Skipping updating host keys (they are already defined in config)'); + exit(0); +} + +$api = new Api($config, null, $output); + +if (!$api->isLoggedIn()) { + $stdErr->writeln('Skipping updating the list of regions from the API (not logged in)'); + exit(0); +} + +$stdErr->writeln('Fetching the list of regions from the API...'); +$regions = $api->getClient()->getRegions(); + +$regionDomains = []; +foreach ($regions as $region) { + if ($region->private) { + continue; + } + $domain = \parse_url($region->endpoint, PHP_URL_HOST); + if (!$domain) { + $stdErr->writeln("Failed to parse hostname for region: " . $region['id']); + continue; + } + $regionDomains[] = $domain; +} + +$stdErr->writeln(\count($regionDomains) . ' region domain(s) found'); + +$scanners = []; +foreach ($regionDomains as $regionDomain) { + foreach (['git.', 'ssh.'] as $prefix) { + $proc = new Process(['ssh-keyscan', $prefix . $regionDomain]); + $proc->start(); + $scanners[$prefix . $regionDomain] = $proc; + } +} + +$stdErr->writeln(\count($scanners) . ' ssh-keyscan processes started'); + +$scannedHosts = []; +while (count($scanners)) { + foreach ($scanners as $host => $scanner) { + if (!$scanner->isRunning()) { + if ($scanner->isSuccessful()) { + $stdErr->writeln('Scanned host ' . $host); + $scannedHosts[$host] = trim($scanner->getOutput()); + } else { + $stdErr->writeln(sprintf('Failed to scan host %s: %s', $host, $scanner->getErrorOutput())); + exit(1); + } + unset($scanners[$host]); + } + } + usleep(300000); +} + +ksort($scannedHosts); + +$fs = new Filesystem(); + +$keys_filename = dirname(__DIR__) . '/resources/ssh/host-keys'; +$fs->dumpFile($keys_filename, implode("\n", array_values($scannedHosts)) . "\n"); +$stdErr->writeln(sprintf('Written to file: %s', $keys_filename)); diff --git a/src/Command/CommandBase.php b/src/Command/CommandBase.php index 8ae88dede2..52d3cf1158 100644 --- a/src/Command/CommandBase.php +++ b/src/Command/CommandBase.php @@ -2132,9 +2132,13 @@ protected function finalizeLogin() $client = $this->api()->getClient(false, true); $this->stdErr->writeln('You are logged in.'); - // Generate a new certificate from the certifier API. /** @var \Platformsh\Cli\Service\SshConfig $sshConfig */ $sshConfig = $this->getService('ssh_config'); + + // Configure SSH host keys. + $sshConfig->configureHostKeys(); + + // Generate a new certificate from the certifier API. /** @var \Platformsh\Cli\SshCert\Certifier $certifier */ $certifier = $this->getService('certifier'); if ($certifier->isAutoLoadEnabled() && $sshConfig->checkRequiredVersion()) { @@ -2149,10 +2153,10 @@ protected function finalizeLogin() } } - // Write SSH configuration. - /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */ - $questionHelper = $this->getService('question_helper'); + // Write session-based SSH configuration. if ($sshConfig->configureSessionSsh()) { + /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */ + $questionHelper = $this->getService('question_helper'); $sshConfig->addUserSshConfig($questionHelper); } diff --git a/src/Command/SshCert/SshCertLoadCommand.php b/src/Command/SshCert/SshCertLoadCommand.php index ae354f7f89..29b6290734 100644 --- a/src/Command/SshCert/SshCertLoadCommand.php +++ b/src/Command/SshCert/SshCertLoadCommand.php @@ -62,6 +62,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->displayCertificate($sshCert); } + $sshConfig->configureHostKeys(); $hasSessionConfig = $sshConfig->configureSessionSsh(); if ($input->getOption('refresh-only')) { diff --git a/src/Service/Ssh.php b/src/Service/Ssh.php index fc2e45e269..0b2fb24943 100644 --- a/src/Service/Ssh.php +++ b/src/Service/Ssh.php @@ -96,6 +96,14 @@ protected function getSshOptions() $options['SendEnv'] = 'TERM'; + if ($this->output->isDebug()) { + $options['LogLevel'] = 'DEBUG'; + } elseif ($this->output->isVeryVerbose()) { + $options['LogLevel'] = 'VERBOSE'; + } elseif ($this->output->isQuiet()) { + $options['LogLevel'] = 'QUIET'; + } + if ($this->input->hasOption('identity-file') && $this->input->getOption('identity-file')) { $file = $this->input->getOption('identity-file'); if (!file_exists($file)) { @@ -144,15 +152,12 @@ protected function getSshOptions() $options['IdentityFile'] = \array_unique($options['IdentityFile']); } - if ($this->output->isDebug()) { - $options['LogLevel'] = 'DEBUG'; - } elseif ($this->output->isVeryVerbose()) { - $options['LogLevel'] = 'VERBOSE'; - } elseif ($this->output->isQuiet()) { - $options['LogLevel'] = 'QUIET'; + // Configure host keys and link them. + if (($keysFile = $this->sshConfig->configureHostKeys()) !== null) { + $options['UserKnownHostsFile'] = '~/.ssh/known_hosts ~/.ssh/known_hosts2 ' . $this->sshConfig->formatFilePath($keysFile); } - // Ensure the session SSH config is up to date. + // Configure or validate the session SSH config. $this->sshConfig->configureSessionSsh(); return $options; diff --git a/src/Service/SshConfig.php b/src/Service/SshConfig.php index 99ea792d8b..5dda65e0d8 100644 --- a/src/Service/SshConfig.php +++ b/src/Service/SshConfig.php @@ -27,6 +27,43 @@ public function __construct(Config $config, Filesystem $fs, OutputInterface $out $this->certifier = $certifier; } + /** + * Creates or updates config files with known host keys. + * + * @return string|null + * The path to the file containing the host keys, if any. + */ + public function configureHostKeys() + { + $keysSourceFile = (string) $this->config->getWithDefault('api.ssh_host_keys_file', ''); + if (!$keysSourceFile) { + return null; + } + if (!(new \Symfony\Component\Filesystem\Filesystem())->isAbsolutePath($keysSourceFile)) { + $keysSourceFile = CLI_ROOT . DIRECTORY_SEPARATOR . $keysSourceFile; + } + $hostKeys = ''; + if (file_exists($keysSourceFile)) { + $hostKeys = file_get_contents($keysSourceFile); + } + if (!$hostKeys) { + return null; + } + + // Write the keys. + $keysFile = $this->getCliSshDir() . DIRECTORY_SEPARATOR . 'host-keys'; + $this->writeSshIncludeFile($keysFile, $hostKeys); + + // Write the config. + // Any .config files will be included automatically as SSH config. + if ($this->supportsInclude()) { + $keysConfigFile = $keysFile . '.config'; + $this->writeSshIncludeFile($keysConfigFile, ['UserKnownHostsFile ~/.ssh/known_hosts ~/.ssh/known_hosts2 ' . $this->formatFilePath($keysFile)]); + } + + return $keysFile; + } + /** * Creates or updates session-specific SSH configuration. * @@ -151,17 +188,17 @@ public function formatFilePath($path) * Creates or updates an SSH config include file. * * @param string $filename - * @param array $lines + * @param array|string $lines * @param bool $allowDelete */ - private function writeSshIncludeFile($filename, array $lines, $allowDelete = true) + private function writeSshIncludeFile($filename, $lines, $allowDelete = true) { if (empty($lines) && $allowDelete && \file_exists($filename)) { $this->stdErr->writeln(sprintf('Deleting SSH configuration include file: %s', $filename), OutputInterface::VERBOSITY_VERBOSE); $this->fs->remove($filename); return; } - $contents = implode(PHP_EOL, $lines) . PHP_EOL; + $contents = is_array($lines) ? implode(PHP_EOL, $lines) . PHP_EOL : $lines; if (!\file_exists($filename)) { $this->stdErr->writeln(sprintf('Creating SSH configuration include file: %s', $filename), OutputInterface::VERBOSITY_VERBOSE); $this->fs->writeFile($filename, $contents, false);