Skip to content

Commit

Permalink
subiquity.network: cloud-init networking when netplan root-readonly
Browse files Browse the repository at this point in the history
When cloudinit.features.NETPLAN_CONFIG_ROOT_READ_ONLY is True,
cloud-init will write /etc/netplan/50-cloud-init.yaml as read-only
root.

This added security allows for subiquity to use cloud-init's
network renderer directly allowing both datasource and network
configuration passed in one place.

Read cloud-init features from
/run/cloud-init/combined-cloud-config.json when present.

Any netplan wifi configuration can be specified in a single
root-read-only network config file
/etc/cloud/cloud.cfg.d/90-installer-network.cfg instead of
having a separate config file for wifi, which could contain
credentials.

This simplifies golden image creation from images installed using
subiquity because image builders will not need to track down and
purge separate /etc/netplan/00-installer-config.yaml and
/etc/netplan/subiquity-disable-cloudinit-networking.cfg when preparing
a golden image.

Eventually, netplan config validation and cloudinit will support
separation of sensitive configuration by cloud-init without needing
to pre-categorize sensitive information.

This will allow cloud-init to grow to ability to write separate
world-readable configuration from config which is security sensitive
with no change needed in subiquity.
  • Loading branch information
blackboxsw committed Jul 7, 2023
1 parent 00c65f7 commit 0ffded7
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 154 deletions.
81 changes: 57 additions & 24 deletions subiquity/models/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import json
import logging
import subprocess

from subiquitycore.models.network import NetworkModel as CoreNetworkModel
from subiquitycore.utils import arun_command

log = logging.getLogger('subiquity.models.network')
log = logging.getLogger("subiquity.models.network")


class NetworkModel(CoreNetworkModel):

def __init__(self):
super().__init__("subiquity")
self.override_config = None
Expand All @@ -34,53 +34,86 @@ def render_config(self):
else:
return super().render_config()

def _host_combined_cloud_config(self) -> dict:
"""Return the host system /run/cloud-init/combined-cloud-config.json"""
try:
with open("/run/cloud-init/combined-cloud-config.json") as fp:
return json.load(fp)
except (IOError, OSError, AttributeError, json.decoder.JSONDecodeError):
return {}

def render(self):
netplan = self.render_config()
# We write wifi config -- which almost certainly contains secrets -- to
# a separate file with more restrictive permissions. This isn't a
# perfect solution because in principle there could be wired 802.1x
# stuff that has secrets too but the subiquity UI does not support any
# of that yet so this will do for now.
wifis = netplan['network'].pop('wifis', None)
r = {
'write_files': {
'etc_netplan_installer': {
'path': 'etc/netplan/00-installer-config.yaml',
'content': self.stringify_config(netplan),

# If host cloud-init version has no readable combined-cloud-config,
# default to False.
cloud_cfg = self._host_combined_cloud_config()
use_cloudinit_net = cloud_cfg.get("features", {}).get(
"NETPLAN_CONFIG_ROOT_READ_ONLY", False
)

if use_cloudinit_net:
r = {
"write_files": {
"etc_netplan_installer": {
"path": ("etc/cloud/cloud.cfg.d" "/90-installer-network.cfg"),
"content": self.stringify_config(netplan),
},
}
}
else:
# Separate sensitive wifi config from world-readable config
wifis = netplan["network"].pop("wifis", None)
r = {
"write_files": {
# Disable cloud-init networking
"no_cloudinit_net": {
"path": (
"etc/cloud/cloud.cfg.d/"
"subiquity-disable-cloudinit-networking.cfg"
),
"content": "network: {config: disabled}\n",
},
'nonet': {
'path': ('etc/cloud/cloud.cfg.d/'
'subiquity-disable-cloudinit-networking.cfg'),
'content': 'network: {config: disabled}\n',
# World-readable netplan without sensitive wifi config
"etc_netplan_installer": {
"path": "etc/netplan/00-installer-config.yaml",
"content": self.stringify_config(netplan),
},
},
}
if wifis is not None:
netplan_wifi = {
'network': {
'version': 2,
'wifis': wifis,
if wifis is not None:
netplan_wifi = {
"network": {
"version": 2,
"wifis": wifis,
},
}
r['write_files']['etc_netplan_installer_wifi'] = {
'path': 'etc/netplan/00-installer-config-wifi.yaml',
'content': self.stringify_config(netplan_wifi),
'permissions': '0600',
r["write_files"]["etc_netplan_installer_wifi"] = {
"path": "etc/netplan/00-installer-config-wifi.yaml",
"content": self.stringify_config(netplan_wifi),
"permissions": "0600",
}
return r

async def target_packages(self):
if self.needs_wpasupplicant:
return ['wpasupplicant']
return ["wpasupplicant"]
else:
return []

async def is_nm_enabled(self):
try:
cp = await arun_command(("nmcli", "networking"), check=True)
except subprocess.CalledProcessError as exc:
log.warning("failed to run nmcli networking,"
" considering NetworkManager disabled.")
log.warning(
"failed to run nmcli networking,"
" considering NetworkManager disabled."
)
log.debug("stderr: %s", exc.stderr)
return False
except FileNotFoundError:
Expand Down
Loading

0 comments on commit 0ffded7

Please sign in to comment.