mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge pull request #404 from YunoHost/authorized_keys
[enh] add commands to manage authorized-keys of users
This commit is contained in:
commit
c992085009
3 changed files with 202 additions and 0 deletions
|
@ -1349,6 +1349,74 @@ dyndns:
|
|||
api: DELETE /dyndns/cron
|
||||
|
||||
|
||||
#############################
|
||||
# SSH #
|
||||
#############################
|
||||
ssh:
|
||||
category_help: Manage ssh keys and access
|
||||
actions: {}
|
||||
subcategories:
|
||||
authorized-keys:
|
||||
subcategory_help: Manage user's authorized ssh keys
|
||||
|
||||
actions:
|
||||
### ssh_authorized_keys_list()
|
||||
list:
|
||||
action_help: Show user's authorized ssh keys
|
||||
api: GET /ssh/authorized-keys
|
||||
configuration:
|
||||
authenticate: all
|
||||
arguments:
|
||||
username:
|
||||
help: Username of the user
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
|
||||
### ssh_authorized_keys_add()
|
||||
add:
|
||||
action_help: Add a new authorized ssh key for this user
|
||||
api: POST /ssh/authorized-keys
|
||||
configuration:
|
||||
authenticate: all
|
||||
arguments:
|
||||
username:
|
||||
help: Username of the user
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
-u:
|
||||
full: --public
|
||||
help: Public key
|
||||
extra:
|
||||
required: True
|
||||
-i:
|
||||
full: --private
|
||||
help: Private key
|
||||
extra:
|
||||
required: True
|
||||
-n:
|
||||
full: --name
|
||||
help: Key name
|
||||
extra:
|
||||
required: True
|
||||
|
||||
### ssh_authorized_keys_remove()
|
||||
remove:
|
||||
action_help: Remove an authorized ssh key for this user
|
||||
api: DELETE /ssh/authorized-keys
|
||||
configuration:
|
||||
authenticate: all
|
||||
arguments:
|
||||
username:
|
||||
help: Username of the user
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
-k:
|
||||
full: --key
|
||||
help: Key as a string
|
||||
extra:
|
||||
required: True
|
||||
|
||||
|
||||
#############################
|
||||
# Tools #
|
||||
#############################
|
||||
|
|
102
src/yunohost/ssh.py
Normal file
102
src/yunohost/ssh.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
# encoding: utf-8
|
||||
|
||||
import os
|
||||
|
||||
from moulinette.utils.filesystem import read_file, write_to_file, chown, chmod, mkdir
|
||||
|
||||
from yunohost.user import _get_user_for_ssh
|
||||
|
||||
|
||||
def ssh_authorized_keys_list(auth, username):
|
||||
user = _get_user_for_ssh(auth, username, ["homeDirectory"])
|
||||
if not user:
|
||||
raise Exception("User with username '%s' doesn't exists" % username)
|
||||
|
||||
authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys")
|
||||
|
||||
if not os.path.exists(authorized_keys_file):
|
||||
return []
|
||||
|
||||
keys = []
|
||||
last_comment = ""
|
||||
for line in read_file(authorized_keys_file).split("\n"):
|
||||
# empty line
|
||||
if not line.strip():
|
||||
continue
|
||||
|
||||
if line.lstrip().startswith("#"):
|
||||
last_comment = line.lstrip().lstrip("#").strip()
|
||||
continue
|
||||
|
||||
# assuming a key per non empty line
|
||||
key = line.strip()
|
||||
keys.append({
|
||||
"key": key,
|
||||
"name": last_comment,
|
||||
})
|
||||
|
||||
last_comment = ""
|
||||
|
||||
return {"keys": keys}
|
||||
|
||||
|
||||
def ssh_authorized_keys_add(auth, username, key, comment):
|
||||
user = _get_user_for_ssh(auth, username, ["homeDirectory", "uid"])
|
||||
if not user:
|
||||
raise Exception("User with username '%s' doesn't exists" % username)
|
||||
|
||||
authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys")
|
||||
|
||||
if not os.path.exists(authorized_keys_file):
|
||||
# ensure ".ssh" exists
|
||||
mkdir(os.path.join(user["homeDirectory"][0], ".ssh"),
|
||||
force=True, parents=True, uid=user["uid"][0])
|
||||
|
||||
# create empty file to set good permissions
|
||||
write_to_file(authorized_keys_file, "")
|
||||
chown(authorized_keys_file, uid=user["uid"][0])
|
||||
chmod(authorized_keys_file, 0600)
|
||||
|
||||
authorized_keys_content = read_file(authorized_keys_file)
|
||||
|
||||
authorized_keys_content += "\n"
|
||||
authorized_keys_content += "\n"
|
||||
|
||||
if comment and comment.strip():
|
||||
if not comment.lstrip().startswith("#"):
|
||||
comment = "# " + comment
|
||||
authorized_keys_content += comment.replace("\n", " ").strip()
|
||||
authorized_keys_content += "\n"
|
||||
|
||||
authorized_keys_content += key.strip()
|
||||
authorized_keys_content += "\n"
|
||||
|
||||
write_to_file(authorized_keys_file, authorized_keys_content)
|
||||
|
||||
|
||||
def ssh_authorized_keys_remove(auth, username, key):
|
||||
user = _get_user(auth, username, ["homeDirectory", "uid"])
|
||||
if not user:
|
||||
raise Exception("User with username '%s' doesn't exists" % username)
|
||||
|
||||
authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys")
|
||||
|
||||
if not os.path.exists(authorized_keys_file):
|
||||
raise Exception("this key doesn't exists ({} dosesn't exists)".format(authorized_keys_file))
|
||||
|
||||
authorized_keys_content = read_file(authorized_keys_file)
|
||||
|
||||
if key not in authorized_keys_content:
|
||||
raise Exception("Key '{}' is not present in authorized_keys".format(key))
|
||||
|
||||
# don't delete the previous comment because we can't verify if it's legit
|
||||
|
||||
# this regex approach failed for some reasons and I don't know why :(
|
||||
# authorized_keys_content = re.sub("{} *\n?".format(key),
|
||||
# "",
|
||||
# authorized_keys_content,
|
||||
# flags=re.MULTILINE)
|
||||
|
||||
authorized_keys_content = authorized_keys_content.replace(key, "")
|
||||
|
||||
write_to_file(authorized_keys_file, authorized_keys_content)
|
|
@ -36,10 +36,13 @@ import subprocess
|
|||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_file
|
||||
from yunohost.service import service_status
|
||||
|
||||
logger = getActionLogger('yunohost.user')
|
||||
|
||||
SSHD_CONFIG_PATH = "/etc/ssh/sshd_config"
|
||||
|
||||
|
||||
def user_list(auth, fields=None):
|
||||
"""
|
||||
|
@ -58,6 +61,7 @@ def user_list(auth, fields=None):
|
|||
'mail': 'mail',
|
||||
'maildrop': 'mail-forward',
|
||||
'loginShell': 'shell',
|
||||
'homeDirectory': 'home_path',
|
||||
'mailuserquota': 'mailbox-quota'
|
||||
}
|
||||
|
||||
|
@ -511,6 +515,34 @@ def _hash_user_password(password):
|
|||
|
||||
|
||||
def _get_user_for_ssh(auth, username, attrs=None):
|
||||
def ssh_root_login_status(auth):
|
||||
# XXX temporary placed here for when the ssh_root commands are integrated
|
||||
# extracted from https://github.com/YunoHost/yunohost/pull/345
|
||||
# XXX should we support all the options?
|
||||
# this is the content of "man sshd_config"
|
||||
# PermitRootLogin
|
||||
# Specifies whether root can log in using ssh(1). The argument must be
|
||||
# “yes”, “without-password”, “forced-commands-only”, or “no”. The
|
||||
# default is “yes”.
|
||||
sshd_config_content = read_file(SSHD_CONFIG_PATH)
|
||||
|
||||
if re.search("^ *PermitRootLogin +(no|forced-commands-only) *$",
|
||||
sshd_config_content, re.MULTILINE):
|
||||
return {"PermitRootLogin": False}
|
||||
|
||||
return {"PermitRootLogin": True}
|
||||
|
||||
if username == "root":
|
||||
root_unix = pwd.getpwnam("root")
|
||||
return {
|
||||
'username': 'root',
|
||||
'fullname': '',
|
||||
'mail': '',
|
||||
'ssh_allowed': ssh_root_login_status(auth)["PermitRootLogin"],
|
||||
'shell': root_unix.pw_shell,
|
||||
'home_path': root_unix.pw_dir,
|
||||
}
|
||||
|
||||
if username == "admin":
|
||||
admin_unix = pwd.getpwnam("admin")
|
||||
return {
|
||||
|
|
Loading…
Add table
Reference in a new issue