Commit 35a81342 authored by Dermot Bradley's avatar Dermot Bradley
Browse files

community/cloud-init: Add doas support

Remove sudo from cloud-init dependancy list.
Add doas support to cc_users_groups module.
Update README.Alpine to add details regarding sudo and doas support.
parent b28a65c9
Pipeline #93385 passed with stages
in 6 minutes and 13 seconds
From: Dermot Bradley <dermot_bradley@yahoo.com>
Date: Thu, 26 Aug 2021 00:58 +0100
Subject: [PATCH] cloud-init: Add doas support
Add doas support to users_groups module.
---
diff -aur a/cloudinit/config/cc_users_groups.py b/cloudinit/config/cc_users_groups.py
--- a/cloudinit/config/cc_users_groups.py
+++ b/cloudinit/config/cc_users_groups.py
@@ -26,6 +26,9 @@
config keys for an entry in ``users`` are as follows:
- ``name``: The user's login name
+ - ``doas``: Optional. Doas rule to use, list of Doas rules to use or False.
+ Default: none. An absence of doas key, or a value of none or false
+ will result in no doas rules being written for the user.
- ``expiredate``: Optional. Date on which the user's account will be
disabled. Default: none
- ``gecos``: Optional. Comment about the user, usually a comma-separated
@@ -76,14 +79,20 @@
if the cloud-config can be intercepted. SSH authentication is preferred.
.. note::
+ If specifying a doas rule for a user, ensure that the syntax for the rule
+ is valid, as the only checking performed by cloud-init is to ensure that
+ the user referenced in the rule is the correct user.
+
+.. note::
If specifying a sudo rule for a user, ensure that the syntax for the rule
is valid, as it is not checked by cloud-init.
.. note::
Most of these configuration options will not be honored if the user
already exists. The following options are the exceptions; they are applied
- to already-existing users: ``plain_text_passwd``, ``hashed_passwd``,
- ``lock_passwd``, ``sudo``, ``ssh_authorized_keys``, ``ssh_redirect_user``.
+ to already-existing users: ``plain_text_passwd``, ``doas``,
+ ``hashed_passwd``, ``lock_passwd``, ``sudo``, ``ssh_authorized_keys``,
+ ``ssh_redirect_user``.
**Internal name:** ``cc_users_groups``
@@ -103,6 +112,7 @@
- name: <some_restricted_user>
sudo: false
- name: <username>
+ doas: <doas config>
expiredate: '<date>'
gecos: <comment>
groups: <additional groups>
diff -aur a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -71,6 +71,7 @@
usr_lib_exec = "/usr/lib"
hosts_fn = "/etc/hosts"
+ ci_doas_fn = "/etc/doas.d/cloud-init.conf"
ci_sudoers_fn = "/etc/sudoers.d/90-cloud-init-users"
hostname_conf_fn = "/etc/hostname"
tz_zone_dir = "/usr/share/zoneinfo"
@@ -567,6 +568,7 @@
* ``plain_text_passwd``
* ``hashed_passwd``
* ``lock_passwd``
+ * ``doas``
* ``sudo``
* ``ssh_authorized_keys``
* ``ssh_redirect_user``
@@ -592,6 +594,10 @@
if kwargs.get('lock_passwd', True):
self.lock_passwd(name)
+ # Configure doas access
+ if 'doas' in kwargs and kwargs['doas'] is not False:
+ self.write_doas_rules(name, kwargs['doas'])
+
# Configure sudo access
if 'sudo' in kwargs and kwargs['sudo'] is not False:
self.write_sudo_rules(name, kwargs['sudo'])
@@ -673,6 +679,85 @@
return True
+ def ensure_doas_dir(self, path):
+ # Ensure the directory actually exists
+ util.ensure_dir(path, 0o750)
+
+ def is_doas_rule_valid(self, user, rule):
+ rule_pattern = r"^(?:permit|deny)" + \
+ r"(?:\s+(?:nolog|nopass|persist|keepenv|setenv \{[^}]+\})+)*" + \
+ r"\s+([a-zA-Z0-9_]+)+" + \
+ r"(?:\s+as\s+[a-zA-Z0-9_]+)*" + \
+ r"(?:\s+cmd\s+[^\s]+(?:\s+args\s+[^\s]+(?:\s*[^\s]+)*)*)*" + \
+ r"\s*$"
+
+ LOG.debug("Checking if user '%s' is referenced in doas rule %r",
+ user, rule)
+
+ valid_match = re.search(rule_pattern, rule)
+ if valid_match:
+ LOG.debug("User '%s' referenced in doas rule",
+ valid_match.group(1))
+ if valid_match.group(1) == user:
+ LOG.debug("Correct user is referenced in doas rule")
+ return True
+ else:
+ LOG.debug("Incorrect user '%s' is referenced in doas rule",
+ valid_match.group(1))
+ return False
+ else:
+ LOG.debug("Doas rule does not appear to reference any user")
+ return False
+
+ def write_doas_rules(self, user, rules, doas_file=None):
+ if not doas_file:
+ doas_file = self.ci_doas_fn
+
+ if isinstance(rules, (list, tuple)):
+ for rule in rules:
+ if not self.is_doas_rule_valid(user, rule):
+ msg = "Invalid Doas rule %r for user '%s', not writing any Doas rules for user!" % (rule, user)
+ LOG.error(msg)
+ return
+ elif isinstance(rules, str):
+ if not self.is_doas_rule_valid(user, rule):
+ msg = "Invalid Doas rule %r for user '%s', not writing any Doas rules for user!" % (rule, user)
+ LOG.error(msg)
+ return
+
+ lines = [
+ '',
+ "# User rules for %s" % user,
+ ]
+ if isinstance(rules, (list, tuple)):
+ for rule in rules:
+ lines.append("%s" % rule)
+ elif isinstance(rules, str):
+ lines.append("%s" % rules)
+ else:
+ msg = "Can not create Doas rule addition with type %r"
+ raise TypeError(msg % (type_utils.obj_name(rules)))
+ content = "\n".join(lines)
+ content += "\n" # trailing newline
+
+ self.ensure_doas_dir(os.path.dirname(doas_file))
+ if not os.path.exists(doas_file):
+ contents = [
+ util.make_header(),
+ content,
+ ]
+ try:
+ util.write_file(doas_file, "\n".join(contents), 0o440)
+ except IOError as e:
+ util.logexc(LOG, "Failed to write Doas file %s", doas_file)
+ raise e
+ else:
+ try:
+ util.append_file(doas_file, content)
+ except IOError as e:
+ util.logexc(LOG, "Failed to append Doas file %s", doas_file)
+ raise e
+
def ensure_sudo_dir(self, path, sudo_base='/etc/sudoers'):
# Ensure the dir is included and that
# it actually exists as a directory
diff -aur a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl
--- a/config/cloud.cfg.tmpl
+++ b/config/cloud.cfg.tmpl
@@ -245,10 +245,13 @@
lock_passwd: True
gecos: {{ variant }} Cloud User
{% endif %}
+{% if variant == "alpine" %}
+ doas: ["permit persist alpine"]
+{% endif %}
{% if variant == "suse" %}
groups: [cdrom, users]
{% elif variant == "alpine" %}
- groups: [adm, sudo]
+ groups: [adm, sudo, wheel]
{% elif variant == "arch" %}
groups: [wheel, users]
{% else %}
......@@ -3,7 +3,7 @@
# Maintainer: Dermot Bradley <dermot_bradley@yahoo.com>
pkgname=cloud-init
pkgver=21.3
pkgrel=0
pkgrel=1
pkgdesc="Cloud instance init scripts"
url="https://cloud-init.io"
# Dependant package "cloud-utils-growpart" is not available for mips,
......@@ -29,7 +29,6 @@ depends="
sfdisk
sgdisk
shadow
sudo
tzdata
"
makedepends="py3-setuptools"
......@@ -40,6 +39,7 @@ source="cloud-init-$pkgver.tar.gz::https://github.com/canonical/cloud-init/archi
03-disable-irrelevant-modules.patch
04-hook-hotplug-sh.patch
05-apk-upgrade.patch
06-add-doas.patch
cloud-init-hotplugd
cloud-init-hotplugd.initd
cloud-init.logrotate
......@@ -164,10 +164,11 @@ c14ce3b27c55d1c381e27b2898bb73da8a74c53480d496bdfc87318b69fb612678205ab5a686c1ce
0efbd2b6f0ec4087938e8574d0b78f1b33605e61294159ea74118d1a583380e38c06ee5fd5fb54e8d64259b6b799d95677ec846eff1296a0ab71d76c7ca06d77 03-disable-irrelevant-modules.patch
3e3f3710d45e6525fd3537afef66c0006892d740bd84ce6052da298f5179170d2f35e43071499cf84fd9646ef8e7a21e2bd0829ce390691eb5db04fb32cc584d 04-hook-hotplug-sh.patch
fd3819625f10157e6a88d3c68402864ff29a659bc63535d182086c3b36294f3362770fa8ee9deb77621f92d39b9640d14d0ce910c3bb5924c104931a2fd23540 05-apk-upgrade.patch
faf79e9d2875b7ff7d79a1b8105d9a8fb83bfeac75b4224affc8600c7ae1d38a52c62c41d4eaf6d6146553e3389e66a8c7df4ef5fcab2b818d35c12dfb43bf3d 06-add-doas.patch
2d41ec3c43f3a426b3c59526dbd34e4a6dd73c894dfe4b699a0f302c12cef3eab6c9300eb126010d00310a12843f56e49d0b1a088f320890d51fb905c3375bb7 cloud-init.logrotate
2d7b80fc248ec18f20c4ed2fbe0f8d6ea85ae57a0ece8ecf8a095cf910c576807c19ab0453c00160b85253df5725cba90abec731cb54f441a509611115519b46 cloud-init-hotplugd
ab44fc51979a5da8569b08ed2f290a6610de6c36cf147af20f30e9224847623d3594c056a1f64c614598ac130e9bec92d566fb901024f806b768a89190c45f59 cloud-init-hotplugd.initd
48b25ec4457c2b3772a7d210033551d041749a0d1869818d888030e6df7fd9bbc13a38b95cf465de3d46d96881a722f94a337584ce48f280c4a52b819586563a interfaces
f32b90f26d1df0d88fbea7a1a9ab6704a20babdb750b933484bb7c4e91cc19a587d4bfbc95489a354003ecd00795d1592e71b4b93eccd41e392bb7170bfebb0e setup-cloud-init
aa3ecfb53c115996327fb04a21aa569d93e02a18918b27ad7d12738937c6ee999ea6ac2aa92e0abd0e0ea547e00fd89a600b83b5b209adcbced83c41776e7fc8 README.Alpine
aaf419fc3b24be075f055d6560709c5f0612af79045bb4872aa7011cb26d02284ae44084ea323ee819f0910e8bf7c82ac8f907030f32219cd74a4f770a8e5ff0 README.Alpine
"
......@@ -8,6 +8,53 @@ used by cloud-init for both disk configuration and network card persistent
naming.
Sudo & Doas
-----------
Cloud-init has always had support for 'sudo' when adding users (via
user-data). As Alpine is now moving towards preferring the use of 'doas'
rather than 'sudo' support for 'doas' has been added to the cc_users_groups
module.
As a result, the Alpine cloud-init package no longer declares a dependency
on sudo - you must ensure to install either the 'doas' or 'sudo' package (or
indeed both), depending on which you wish to use, in order to be able to
create users that can run commands as a privileged user.
Doas
----
The cloud-init support for Doas has not (yet) been upstreamed, it only
exists in the Alpine cloud-init package. This functionality performs some
basic sanity checks of per-user Doas rules - it double-checks that the user
referred to in any such rules corresponds to the user the rule(s) is/are
defined for - if not then an error appears during 1st boot and no Doas rules
are added for that user.
It is recommended that you set up a Doas rule: "permit persist :wheel" so
that all members of group 'wheel' can have root access. The default
/etc/doas.d/doas.conf file has such a rule but it is commented out. The
/cloud-init global configuration file /etc/cloud/cloud.cfg defines the default
'alpine' user to be created upon 1st boot with a Doas rule giving root access.
To setup Doas rules for additional users, both existing and new, in the
user-data add a 'doas' entry to define one or more rules to be setup for
each, for example:
users:
- default
- name: tester
doas: ["permit tester as root"]
When cloud-init runs on 1st boot it creates the file
/etc/doas.d/cloud-init.conf containing all the per-user rules specified in
user-data, as well as the rule for the default 'alpine' user.
NTP
---
......@@ -73,6 +120,7 @@ To use Busybox as the NTP client:
as a template to create the configuration file /etc/ntp.conf.
Network interface hotplugging
-----------------------------
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment