Skip to content

Commit 61928aa

Browse files
committed
Fix SSH key path for Administrator accounts
Write SSH public keys for the built-in Administrator account to C:\ProgramData\ssh\administrators_authorized_keys instead of C:\Users\Administrator\.ssh\authorized_keys. The default Windows OpenSSH sshd_config uses a Match Group directive that reads admin keys from the ProgramData path: Match Group administrators AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys The previous behavior wrote keys to ~/.ssh/authorized_keys which: 1. Requires the user profile to exist (fails after sysprep before first login — the ProfileList registry entry is missing) 2. Is ignored by sshd for admin users due to the Match Group override The ProgramData path is a system directory that always exists, does not depend on user profiles, and is where all major cloud providers (AWS EC2Launch v2, Azure) write admin SSH keys. Also set proper ACL on administrators_authorized_keys per Microsoft docs: only BUILTIN\Administrators and NT AUTHORITY\SYSTEM should have access. For non-admin users, the behavior is unchanged (~/.ssh/authorized_keys). Closes: #162 Signed-off-by: Max Makarov <maxpain@linux.com>
1 parent d63509f commit 61928aa

1 file changed

Lines changed: 48 additions & 14 deletions

File tree

cloudbaseinit/plugins/common/sshpublickeys.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
# under the License.
1414

1515
import os
16+
import subprocess
1617

1718
from oslo_log import log as oslo_logging
1819

1920
from cloudbaseinit import conf as cloudbaseinit_conf
20-
from cloudbaseinit import exception
2121
from cloudbaseinit.osutils import factory as osutils_factory
2222
from cloudbaseinit.plugins.common import base
2323

@@ -28,6 +28,34 @@
2828

2929
class SetUserSSHPublicKeysPlugin(base.BasePlugin):
3030

31+
@staticmethod
32+
def _write_authorized_keys(authorized_keys_path, public_keys):
33+
authorized_keys_dir = os.path.dirname(authorized_keys_path)
34+
if not os.path.exists(authorized_keys_dir):
35+
os.makedirs(authorized_keys_dir)
36+
37+
LOG.info("Writing SSH public keys in: %s" % authorized_keys_path)
38+
with open(authorized_keys_path, 'w') as f:
39+
for public_key in public_keys:
40+
# All public keys are space-stripped.
41+
f.write(public_key + "\n")
42+
43+
@staticmethod
44+
def _set_admin_authorized_keys_acl(authorized_keys_path):
45+
"""Set ACL on administrators_authorized_keys per Microsoft docs.
46+
47+
Only SYSTEM and Administrators should have access.
48+
"""
49+
try:
50+
subprocess.check_call([
51+
"icacls.exe", authorized_keys_path,
52+
"/inheritance:r",
53+
"/grant", "Administrators:F",
54+
"/grant", "SYSTEM:F",
55+
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
56+
except Exception:
57+
LOG.exception("Failed to set ACL on %s" % authorized_keys_path)
58+
3159
def execute(self, service, shared_data):
3260
public_keys = service.get_public_keys()
3361
if not public_keys:
@@ -37,22 +65,28 @@ def execute(self, service, shared_data):
3765
username = service.get_admin_username() or CONF.username
3866

3967
osutils = osutils_factory.get_os_utils()
40-
user_home = osutils.get_user_home(username)
4168

69+
# For users in the Administrators group, write keys to
70+
# C:\ProgramData\ssh\administrators_authorized_keys as per
71+
# the default OpenSSH sshd_config Match Group directive.
72+
# This path does not require the user profile to exist.
73+
if osutils.is_builtin_admin(username):
74+
programdata = os.environ.get("ProgramData", r"C:\ProgramData")
75+
admin_keys_path = os.path.join(
76+
programdata, "ssh", "administrators_authorized_keys")
77+
self._write_authorized_keys(admin_keys_path, public_keys)
78+
self._set_admin_authorized_keys_acl(admin_keys_path)
79+
return base.PLUGIN_EXECUTION_DONE, False
80+
81+
user_home = osutils.get_user_home(username)
4282
if not user_home:
43-
raise exception.CloudbaseInitException("User profile not found!")
83+
LOG.warning("User profile not found for %r, "
84+
"cannot write SSH public keys", username)
85+
return base.PLUGIN_EXECUTION_DONE, False
4486

4587
LOG.debug("User home: %s" % user_home)
46-
47-
user_ssh_dir = os.path.join(user_home, '.ssh')
48-
if not os.path.exists(user_ssh_dir):
49-
os.makedirs(user_ssh_dir)
50-
51-
authorized_keys_path = os.path.join(user_ssh_dir, "authorized_keys")
52-
LOG.info("Writing SSH public keys in: %s" % authorized_keys_path)
53-
with open(authorized_keys_path, 'w') as f:
54-
for public_key in public_keys:
55-
# All public keys are space-stripped.
56-
f.write(public_key + "\n")
88+
authorized_keys_path = os.path.join(
89+
user_home, '.ssh', 'authorized_keys')
90+
self._write_authorized_keys(authorized_keys_path, public_keys)
5791

5892
return base.PLUGIN_EXECUTION_DONE, False

0 commit comments

Comments
 (0)