Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFE] Option to use SystemdCreds to read user+password #459

Open
TriMoon opened this issue Nov 7, 2023 · 4 comments
Open

[RFE] Option to use SystemdCreds to read user+password #459

TriMoon opened this issue Nov 7, 2023 · 4 comments

Comments

@TriMoon
Copy link

TriMoon commented Nov 7, 2023

To enhance the systemd integration (see #79 and systemd/systemd#481) even more, i suggest to add an option to allow automatic reading of System and Service Credentials or SystemdCreds as i like to name them.

  • An option named sd_creds for example, the exact naming is up to you.

When this option is used, pppd should automatically read and use:

  1. The value for user from the SystemdCreds, fe. from the PPPoE-username credential. (the exact naming is up to you)
  2. The value for password from the SystemdCreds, fe. from the PPPoE-password credential. (the exact naming is up to you)

To illustrate the usage and workaround until this functionality is implemented

I'm currently using the below self-made scripts and configs in my System, which i post here for others to use till then:
(Still a W.I.P. but it already works flawlessly)

Click the arrowed sections to expand and view (and be able to copy them)...

PPPoE@.target
# <configdir>/PPPoE@.target
#	SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
# You should enable this unit with the interface as instance name.
#	eg. PPPoE@eth0.target / PPPoE@vlan35.target
# You can disregard the warning about the adition as a dependency to
#	a non-existant unit 'sys-subsystem-net-devices-xxxx.device',
#	this is expected behaviour...
#
# VLAN etc usage:
# Your normal network config should create the virtual network device,
#	so this service automatically gets started after it.
#	eg. a "VLAN=VlanID" inside eth0.network, with a corrosponding
#	"VlanID.netdev" (and "VlanID.network" if needed)
[Unit]
Description=Target for %p connections on %I
Documentation=man:systemd.unit(5)#Specifiers
Documentation=man:systemd.target
# This target is NOT a "SysV run-level" like target.
AllowIsolate=no

# See: https://systemd.io/NETWORK_ONLINE/
After=network.target
BindsTo=sys-subsystem-net-devices-%i.device
After=sys-subsystem-net-devices-%i.device
PartOf=sys-subsystem-net-devices-%i.device
# avoid race waiting for systemd-networkd to configure interface
#	https://github.com/systemd/systemd/issues/481#issuecomment-1010092917
# systemd guarentees MTU is set before activating (carrier) link
#	https://github.com/systemd/systemd/issues/481#issuecomment-1010159176
After=systemd-networkd-wait-online@%i.service

[Install]
WantedBy=sys-subsystem-net-devices-%i.device
# WantedBy=sys-devices-virtual-net-%i.device
PPPoE@.target.d/DefaultInstance.conf
# <configdir>/PPPoE@.target.d/DefaultInstance.conf
#	SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
# Optional: Default instance to enable when the template is attempted
# to be enabled without an instance name, which isn't allowed.
[Install]
DefaultInstance=vlan35
PPPoE@.target.d/KernelCommandLine.conf
# <configdir>/PPPoE@.target.d/KernelCommandLine.conf
#	SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
# Optional: Only allow activation when a special KernelCommandLine option is present.
[Unit]
ConditionKernelCommandLine=pppoe
PPPoE@vlan35.target.d/TurkNet.conf
# <configdir>/PPPoE@vlan35.target.d/TurkNet.conf
#	SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
# Auto start/restart the instance unit below.
# In this case '%j-%i' will be: 'PPPoE-vlan35'
[Unit]
Upholds=%j-%i@TurkNet.service
PPPoE-vlan35@.service
# <configdir>/PPPoE-vlan35@.service
#	SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
# You should make a copy of this template with the interface name as
#	the final component of the prefix as the unit name.
# This final component of the prefix is used as the interface to run the PPPoE
#	connection over.
# Eg. to use "vlan35" as interface name to run the PPPoE over you should name
#	the template:  'PPPoE-vlan35@.service'
#
# To view journal logs of your instance unit:
#	journalctl -u PPPoE-vlan35@MyISP
#
[Unit]
Description=%I connection on PPPoE@%j
Documentation=man:pppd(8)
Documentation=https://github.com/ppp-project/ppp/issues/459
Documentation=https://github.com/systemd/systemd/issues/481#issuecomment-544337575
Documentation=https://github.com/jimdigriz/debian-clearfog-gt-8k#libsystemdsystempppdservice

# Refuse to start without a corrosponding peer file !
# AssertPathExists=/etc/ppp/peers/%I

BindsTo=PPPoE@%j.target
After=PPPoE@%j.target
PartOf=PPPoE@%j.target

# [Install]
# WantedBy=%p.target

[Service]
# https://github.com/ppp-project/ppp/commit/d34159f417620eb7c481bf53f29fe04c86ccd223
# otherwsise you can use 'forking' and replace 'up_sdnotify' with 'updetach'
Type=notify
# Type=oneshot
IPAccounting=yes
# StandardOutput=null

# Environment="pppd_opts0=plugin rp-pppoe.so nic-%J linkname %I ifname %I call %I up_sdnotify"
# Environment="pppd_opts1=persist default-mru nolog noauth debug pppoe-verbose 1 holdoff 5 lcp-echo-adaptive"
Environment="pppd_opts1=default-mru debug pppoe-verbose 1 holdoff 5"
# Maybe add 'noipdefault' but seems NOT needed...
# Environment="pppd_opts2=+ipv6 ipv6cp-use-persistent"
# Optional:
# Environment="pppd_opts3=defaultroute defaultroute6 replacedefaultroute"
# IPv4 default route is needed, IPv6 doesn't seem to need...
Environment="pppd_opts3=defaultroute"

ExecStart=/usr/sbin/pppd $pppd_opts0 $pppd_opts1 $pppd_opts2 $pppd_opts3
ExecReload=/bin/kill -HUP $MAINPID

KillSignal=SIGINT
RestartKillSignal=SIGHUP
RestartSec=5s
TimeoutStopSec=5s
Restart=on-failure

#
# # https://github.com/systemd/systemd/issues/481#issuecomment-544341423
# # Restart=always
# Pppd terminated because it was sent a SIGINT, SIGTERM or SIGHUP signal.
SuccessExitStatus=5
# The link was established successfully and terminated because it was idle.
SuccessExitStatus=12
# The link was established successfully and terminated because the connect time limit was reached.
SuccessExitStatus=13
# Callback was negotiated and an incoming call should arrive shortly.
# SuccessExitStatus=14

# An error was detected in processing the options given, such as two mutually exclusive options being used.
RestartPreventExitStatus=2
# The kernel does not support PPP, for example, the PPP kernel driver is not included or cannot be loaded.
RestartPreventExitStatus=4
# The connect script failed (returned a non-zero exit status).
RestartPreventExitStatus=8
# The PPP negotiation failed, that is, it didn't reach the point where at least one network  protocol  (e.g.  IP) was running.
# RestartPreventExitStatus=10
# The peer system failed (or refused) to authenticate itself.
RestartPreventExitStatus=11
# The init script failed (returned a non-zero exit status).
RestartPreventExitStatus=18

# The PPP negotiation failed, that is, it didn't reach the point where at least one network  protocol  (e.g.  IP) was running.
RestartForceExitStatus=10
# Callback was negotiated and an incoming call should arrive shortly.
RestartForceExitStatus=14
# The link was terminated because the peer is not responding to echo requests.
RestartForceExitStatus=15
# The link was terminated by the modem hanging up.
RestartForceExitStatus=16
# We failed to authenticate ourselves to the peer.
RestartForceExitStatus=19

# HARDENING
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
# allow /etc/ppp/resolv.conf to be written when using 'usepeerdns'
ReadWritePaths=/run/ /etc/ppp/
# https://github.com/systemd/systemd/issues/481#issuecomment-610951209
#ProtectKernelTunables=yes
ProtectControlGroups=yes
SystemCallFilter=~@mount
SystemCallArchitectures=native
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
PPPoE-vlan35@.service.d/KernelCommandLine.conf
# <configdir>/PPPoE-vlan35@.service.d/KernelCommandLine.conf
#	SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
# Optional: Only allow activation when a special KernelCommandLine option is present.
[Unit]
ConditionKernelCommandLine=pppoe
PPPoE-vlan35@TurkNet.service.d/Credentials.conf
# <configdir>/PPPoE-vlan35@TurkNet.service.d/Credentials.conf
# PPPoE-Credentials for TurkNet@vlan35
[Service]
SetCredentialEncrypted=PPPoE-username: \
	Whxqht+dQJax1aZeCGLxmiAAAAABAAAADAAAABAAAADY5KqFUJ+YZhTQOOoAAAAAClClv \
	M7s8F3TQIm0e7+0LufdC+6eFdTvtpDSi9ecJUG1FJivZteUua52jVaZ1PuGL8DoqeVFYQ \
	pL9A2kXo5zduY7QUO10auWbR6B4Q==
SetCredentialEncrypted=PPPoE-password: \
	Whxqht+dQJax1aZeCGLxmiAAAAABAAAADAAAABAAAADEUFVtTvt2IqKxipQAAAAA/IGhP \
	x+PfeA5OiDE/I/O7ARi8X7MHGocMrB216kRlaAX37JSXsU4+hVJFBgfH8MN7VcA6/mThc \
	6BIR09VLIbiaiY
PPPoE-@.service.d/UseCredentials.conf
# <configdir>/PPPoE-@.service.d/UseCredentials.conf
#	SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
[Unit]
Documentation=https://systemd.io/CREDENTIALS

# NOTE: Needed because pppd still lacks ability to read Systemd-Credentials...
[Service]
ExecStartPre= @bash 'AutoConfigGenerator_%p' -c \n"\
	printf \"%%s\\t%%s\\n\" \\\n\
		\"user\" \"$$(<%d/PPPoE-username)\" \\\n\
		\"password\" \"$$(<%d/PPPoE-password)\" \\\n\
		\"plugin\" \"rp-pppoe.so\" \\\n\
		\"nic-%J\" \"\" \\\n\
		\"linkname\" \"%I\" \\\n\
		\"ifname\" \"%I\" \\\n\
		\"up_sdnotify\" \"\" \\\n\
		\"persist\" \"\" \\\n\
		\"nolog\" \"\" \\\n\
		\"noauth\" \"\" \\\n\
		\"lcp-echo-adaptive\" \"\" \\\n\
		\"+ipv6\" \"\" \\\n\
		\"ipv6cp-use-persistent\" \"\" \\\n\
		>/tmp/AutoConfig_%p_%I\n\
"
# ExecStartPre=cat /tmp/AutoConfig_%p_%I
ExecStart=
ExecStart= /usr/sbin/pppd file /tmp/AutoConfig_%p_%I $pppd_opts1 $pppd_opts2 $pppd_opts3
<bin path>/createSystemdCreds-PPPoE
#!/usr/bin/env bash
#	SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
# To use the creds you could use one of:
#	1.
#		Environment=CRED_USERNAME=%d/PPPoE-username
#		Environment=CRED_PASSWORD=%d/PPPoE-password
#		cat $CRED_USERNAME
#		cat $CRED_PASSWORD
#	2.
#		cat %d/PPPoE-username
#		cat %d/PPPoE-password

# See: man systemd-creds
# NOTE: The example in the man-page has a bug !
#	it doesn't output the section header, so we need to !
#
# $1 = username
# $2 = password
function genSystemdCred () {
	local -a opts
	local \
		credName \
		credVal

	# Output the header and section name at start
	printf "%s\n" \
		"# PPPoE-Credentials for ${connection}@${interface}" \
		"[Service]"

	# Output the creds lines
	for credName in username password; do
		case "${credName}" in
		username)	credVal="$1" ;;
		password)	credVal="$2" ;;
		*) ;; # No other posibilities.
		esac

		opts=(
			--pretty
			--name="PPPoE-${credName}"
			encrypt
			# Input = stdin
			-
			# Output = stdout
			-
		)
		# shellcheck disable=2312
		printf "%s" "${credVal}" \
		| systemd-creds "${opts[@]}" \
		| sed -E 's/\s{2,}/\t/g' # Convert multiple white-space by a single tab.
	done
}

function main () {
	local -a opts
	local \
		username \
		password \
		connection \
		interface \
		dropInDir \
		credName

	username="$1"
	password="$2"
	connection="$3"
	interface="$4"

	if test -z "${username}" \
		-o -z "${password}" \
		-o -z "${connection}" \
		-o -z "${interface}"
	then
		printf "%s\n" \
			"Missing arguments !" \
			"Usage: ${0##*/} <username> <password> <connection> <interface>"
		exit 64 # EX_USAGE - Command line usage error
	fi

	# Generate the drop-in dir
	printf -v dropInDir "PPPoE-%s@%s.service.d" \
		"${interface}" \
		"${connection}"

	printf "%s\n" \
		"username: ${username}" \
		"password: ${password}" \
		"connection: ${connection}" \
		"interface: ${interface}" \
		"drop-in dir: ${dropInDir}" \
		""

	# Create the drop-in dir if non-existant.
	if test ! -d "${dropInDir}"; then
		opts=(
			--verbose
			--directory
			--mode=2750
			--group=systemd-network
		)
		install "${opts[@]}" "${dropInDir}"
		# Give access to admins.
		setfacl --modify g:adm:rwX,g:sudo:rwX,d:g:adm:rwX,d:g:sudo:rwX "${dropInDir}"
	fi

	genSystemdCred \
		"${username}" \
		"${password}" \
		>"${dropInDir}/Credentials.conf"
}

# We need to run as ROOT !
# shellcheck disable=2312
if test "$(id -u)" -ne 0; then
	exec sudo "$0" "$@"
else
	main "$@"
fi

The last three are the SystemdCreds specific parts obviously 😉

  • The PPPoE-vlan35@TurkNet.service.d/Credentials.conf dir+file was auto-generated using the createSystemdCreds-PPPoE script...
    ⚠️ It will NOT work for you as-is, so you need to generate your own !
  • The PPPoE-@.service.d/UseCredentials.conf drop-in overrides the ExecStart of the main template to read a config file that is auto-generated to implement the automatic reading and usage of the SystemdCreds.
    As you can see the is FAR from optimal because it uses a temporary file which can be eliminated if the functionality asked-for is implemented.
    (It is still relatively safe to use, because of the private tmp used in the hardening this file is only readable by ROOT...)

Note

I use the below parts for tearing down DynNS config.

If you don't make/need use of that functionality; you can safely disregard these parts, and you have no need to install the
/etc/ppp/ip-pre-down and /etc/ppp/ipv6-pre-down scripts with accompanying directories they use...

PPPoE-@.service.d/DynNS-TearDown.conf
# <configdir>/PPPoE-@.service.d/DynNS-TearDown.conf
#	SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
# For DynNS teardown BEFORE the connection gets terminated...
[Service]
# ExecStop= -run-parts /etc/ppp/ip-pre-down.d
ExecStop= -/etc/ppp/ip-pre-down %I
# ExecStop= -run-parts /etc/ppp/ipv6-pre-down.d
ExecStop= -/etc/ppp/ipv6-pre-down %I
# ExecStop=/bin/kill $MAINPID
/etc/ppp/ip-pre-down
#!/bin/sh

# The environment is cleared before executing this script
# so the path must be reset.
PATH=/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin
export PATH

# These variables are for the use of the scripts run by run-parts
PPP_IFACE="$1"
export PPP_IFACE

# If /var/log/ppp-ipupdown.log exists use it for logging.
if [ -e /var/log/ppp-ipupdown.log ]; then
	exec >> /var/log/ppp-ipupdown.log 2>&1
	echo "$0" "$@"
	echo
fi

# This script can be used to override the .d files supplied by other packages.
if [ -x /etc/ppp/ip-pre-down.local ]; then
	exec /etc/ppp/ip-pre-down.local "$@"
fi

run-parts /etc/ppp/ip-pre-down.d \
	--arg="$1"
/etc/ppp/ipv6-pre-down
#!/bin/sh

# The environment is cleared before executing this script
# so the path must be reset.
PATH=/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin
export PATH

# These variables are for the use of the scripts run by run-parts
PPP_IFACE="$1"
export PPP_IFACE

# If /var/log/ppp-ipupdown.log exists use it for logging.
if [ -e /var/log/ppp-ipupdown.log ]; then
	exec >> /var/log/ppp-ipupdown.log 2>&1
	echo "$0" "$@"
	echo
fi

# This script can be used to override the .d files supplied by other packages.
if [ -x /etc/ppp/ipv6-pre-down.local ]; then
	exec /etc/ppp/ipv6-pre-down.local "$@"
fi

run-parts /etc/ppp/ipv6-pre-down.d \
	--arg="$1"

Note

To @ppp-project members:
You might consider adding this pre-down functionality to pppd itself, so they get automatically called BEFORE the connection gets teared down, if wanted by the user. 🤝


And i use these systemd-networked files for the connection configs:
(My onboard Ethernet connection is renamed to utp)

21-pppoe-vlan35.netdev
# <includedir>/.21-pppoe-vlan35.netdev
[NetDev]
Description=Internet VLAN of ISP.
Name=vlan35
Kind=vlan

[VLAN]
Id=35
GVRP=yes
MVRP=yes
ReorderHeader=yes

# <includedir>/.21-pppoe-vlan35.netdev.d/00-Match: KernelCommandLine=pppoe.conf
[Match]
KernelCommandLine=pppoe
21-pppoe-vlan35.network
# <includedir>/.21-pppoe-vlan35.network
[Match]
Name=vlan35

[Network]
Description=Internet VLAN of ISP.

# <includedir>/.21-pppoe-vlan35.network.d/00-Match: KernelCommandLine=pppoe.conf
[Match]
KernelCommandLine=pppoe

# <includedir>/.21-pppoe-vlan35.network.d/01-Link: ARP=yes.conf
[Link]
ARP=yes

# <includedir>/.21-pppoe-vlan35.network.d/01-Link: AllMulticast=yes.conf
[Link]
AllMulticast=yes

# <includedir>/.21-pppoe-vlan35.network.d/01-Link: Group=1.conf
[Link]
Group=1

# <includedir>/.21-pppoe-vlan35.network.d/01-Link: Multicast=yes.conf
[Link]
Multicast=yes

# <includedir>/.21-pppoe-vlan35.network.d/01-Link: RequiredForOnline=carrier.conf
[Link]
RequiredForOnline=carrier

# <includedir>/.21-pppoe-vlan35.network.d/02-Network: BindCarrier=utp.conf
[Network]
BindCarrier=utp

# <includedir>/.21-pppoe-vlan35.network.d/02-Network: LinkLocalAddressing=no.conf
[Network]
LinkLocalAddressing=no
30-pppoe-TurkNet.network
# <includedir>/.30-pppoe-TurkNet.network
[Match]
Name=TurkNet
Type=ppp

# <includedir>/.30-pppoe-TurkNet.network.d/00-Match: KernelCommandLine=pppoe.conf
[Match]
KernelCommandLine=pppoe

# <includedir>/.30-pppoe-TurkNet.network.d/02-Network: BindCarrier=vlan35.conf
[Network]
BindCarrier=vlan35

# <includedir>/.30-pppoe-TurkNet.network.d/02-Network: DHCP=yes.conf
[Network]
DHCP=yes

# <includedir>/.30-pppoe-TurkNet.network.d/04-DHCPv4.conf
[DHCPv4]
Hostname=Linux
UseDNS=no
UseNTP=no
UseMTU=yes
UseDomains=route
SendDecline=yes
UseHostname=no

# <includedir>/.30-pppoe-TurkNet.network.d/04-DHCPv6-uplink.conf
[Network]
IPv6AcceptRA=yes
IPv6PrivacyExtensions=prefer-public
IPv6SendRA=no
DHCPPrefixDelegation=yes

[DHCPv6]
UseHostname=no
UseDNS=no
UseNTP=no

[DHCPPrefixDelegation]
UplinkInterface=:self
SubnetId=::1
Announce=no
Assign=yes
Token=static:::1

[IPv6AcceptRA]
UseDNS=no
UseDomains=route

Note

This config does NOT make use of the DNS/NTP/etc setting provided by the DHCP-Server from the ISP, if you need to make use of those you can tweak the systemd-networkd config options inside.
I use my own settings on my system, that's why i disabled usage of those.


Update:

For easier testing etc of the posted files, i have created a public repo where they can be found.
It will also function as a backup for my own setup 😉
https://gitlab.com/trimoon-inc/system/systemd-PPPoE

@TriMoon TriMoon changed the title [RFE] Option to use SystemdCreds to read username+password [RFE] Option to use SystemdCreds to read user+password Nov 7, 2023
@paulusmack
Copy link
Collaborator

This all sounds reasonable at first glance. What functions would pppd need to call to read the user and password values?

@TriMoon
Copy link
Author

TriMoon commented Nov 9, 2023

@paulusmack
It just needs to read the contents of files inside the $CREDENTIALS_DIRECTORY directory.
See: https://systemd.io/CREDENTIALS/#programming-interface-from-service-code

Note

$CREDENTIALS_DIRECTORY is an environment variable provided for processes executed by the service.
$CREDENTIALS_DIRECTORY = %d in unit files...

If you use the names i used in my examples those files would be:

  • ${CREDENTIALS_DIRECTORY}/PPPoE-username
    (The contents will already be the decrypted value to be used)
  • ${CREDENTIALS_DIRECTORY}/PPPoE-password
    (The contents will already be the decrypted value to be used)

But as said you're free to choose other names, or even let the user choose which names to use 😉

It basically boils down to automatically read the contents of those files and use them "as-if" the user provided those options with their values....
That is what i am actually doing in these lines:

  • PPPoE-@.service.d/UseCredentials.conf
		\"user\" \"$$(<%d/PPPoE-username)\" \\\n\
		\"password\" \"$$(<%d/PPPoE-password)\" \\\n\

@TriMoon
Copy link
Author

TriMoon commented Feb 2, 2024

@paulusmack a followup from your side would be appreciated 😉

@Neustradamus
Copy link
Member

@paulusmack: Have you seen latest @TriMoon comments?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants