diff --git a/recipes-core/packagegroups/packagefeed-ni-core.bb b/recipes-core/packagegroups/packagefeed-ni-core.bb
index ef4b9ba6c..531ab9b9f 100644
--- a/recipes-core/packagegroups/packagefeed-ni-core.bb
+++ b/recipes-core/packagegroups/packagefeed-ni-core.bb
@@ -6,6 +6,9 @@ inherit packagegroup
RDEPENDS:${PN} = "\
packagegroup-base \
packagegroup-core-boot \
+ packagegroup-core-standalone-sdk-target \
+ packagegroup-core-x11 \
+ packagegroup-kernel-module-build \
packagegroup-ni-base \
packagegroup-ni-contributors \
packagegroup-ni-crio \
@@ -16,11 +19,9 @@ RDEPENDS:${PN} = "\
packagegroup-ni-restoremode \
packagegroup-ni-runmode \
packagegroup-ni-safemode \
+ packagegroup-ni-snac \
packagegroup-ni-tzdata \
packagegroup-ni-wifi \
- packagegroup-core-x11 \
- packagegroup-core-standalone-sdk-target \
- packagegroup-kernel-module-build \
dkms \
bolt \
"
diff --git a/recipes-core/packagegroups/packagegroup-ni-ptest-smoke.bb b/recipes-core/packagegroups/packagegroup-ni-ptest-smoke.bb
index b0222d41a..b4a328a8f 100644
--- a/recipes-core/packagegroups/packagegroup-ni-ptest-smoke.bb
+++ b/recipes-core/packagegroups/packagegroup-ni-ptest-smoke.bb
@@ -13,47 +13,48 @@ RDEPENDS:${PN} = "ptest-runner"
# ptest packages
RDEPENDS:${PN}:append = "\
- busybox-ptest \
- bzip2-ptest \
- coreutils-ptest \
- docker-functional-tests-ptest \
- e2fsprogs-ptest \
- elfutils-ptest \
- ethtool-ptest \
- flex-ptest \
- gettext-ptest \
- glibc-locale-tests-ptest \
- glibc-tests-ptest \
- kernel-tests-ptest \
- liberror-perl-ptest \
- libxml2-ptest \
- mdadm-ptest \
- nettle-ptest \
- ni-hw-scripts-ptest \
- ni-test-boot-time-ptest \
- opkg-ptest \
- pango-ptest \
- parted-ptest \
- perl-ptest \
- pstore-save-ptest \
- python3-appdirs-ptest \
- python3-atomicwrites-ptest \
- python3-bcrypt-ptest \
- python3-cryptography-ptest \
- python3-markupsafe-ptest \
- python3-more-itertools-ptest \
- python3-msgpack-ptest \
- python3-multidict-ptest \
- python3-pluggy-ptest \
- python3-pyasn1-ptest \
- python3-pyroute2-ptest \
- python3-pyserial-ptest \
- python3-pytz-ptest \
- python3-wcwidth-ptest \
- rt-tests-ptest \
- run-postinsts-ptest \
- sed-ptest \
- util-linux-ptest \
- xorg-fonts-100dpi-ptest \
- zeromq-ptest \
+ busybox-ptest \
+ bzip2-ptest \
+ coreutils-ptest \
+ docker-functional-tests-ptest \
+ e2fsprogs-ptest \
+ elfutils-ptest \
+ ethtool-ptest \
+ flex-ptest \
+ gettext-ptest \
+ glibc-locale-tests-ptest \
+ glibc-tests-ptest \
+ kernel-tests-ptest \
+ liberror-perl-ptest \
+ libxml2-ptest \
+ mdadm-ptest \
+ nettle-ptest \
+ ni-hw-scripts-ptest \
+ ni-test-boot-time-ptest \
+ nilrt-snac-ptest \
+ opkg-ptest \
+ pango-ptest \
+ parted-ptest \
+ perl-ptest \
+ pstore-save-ptest \
+ python3-appdirs-ptest \
+ python3-atomicwrites-ptest \
+ python3-bcrypt-ptest \
+ python3-cryptography-ptest \
+ python3-markupsafe-ptest \
+ python3-more-itertools-ptest \
+ python3-msgpack-ptest \
+ python3-multidict-ptest \
+ python3-pluggy-ptest \
+ python3-pyasn1-ptest \
+ python3-pyroute2-ptest \
+ python3-pyserial-ptest \
+ python3-pytz-ptest \
+ python3-wcwidth-ptest \
+ rt-tests-ptest \
+ run-postinsts-ptest \
+ sed-ptest \
+ util-linux-ptest \
+ xorg-fonts-100dpi-ptest \
+ zeromq-ptest \
"
diff --git a/recipes-core/packagegroups/packagegroup-ni-runmode.bb b/recipes-core/packagegroups/packagegroup-ni-runmode.bb
index 5c8a92fc8..9c206b01c 100644
--- a/recipes-core/packagegroups/packagegroup-ni-runmode.bb
+++ b/recipes-core/packagegroups/packagegroup-ni-runmode.bb
@@ -23,6 +23,7 @@ RDEPENDS:${PN} = "\
librtpi \
linux-firmware-radeon \
lldpd \
+ nftables \
ni-configpersistentlogs \
ni-locale-alias \
ni-modules-autoload \
diff --git a/recipes-core/packagegroups/packagegroup-ni-snac.bb b/recipes-core/packagegroups/packagegroup-ni-snac.bb
new file mode 100644
index 000000000..0e7b9e784
--- /dev/null
+++ b/recipes-core/packagegroups/packagegroup-ni-snac.bb
@@ -0,0 +1,15 @@
+SUMMARY = "Open source package dependencies for the NILRT SNAC configuration."
+LICENSE = "MIT"
+
+
+inherit packagegroup
+
+
+RDEPENDS:${PN} = "\
+ cryptsetup \
+ firewalld \
+ libpwquality \
+ nilrt-snac \
+ ntp \
+ tmux \
+"
diff --git a/recipes-extended/libpwquality/files/pwquality.conf b/recipes-extended/libpwquality/files/pwquality.conf
new file mode 100644
index 000000000..9a652d5cd
--- /dev/null
+++ b/recipes-extended/libpwquality/files/pwquality.conf
@@ -0,0 +1,78 @@
+# Configuration for systemwide password quality limits
+#
+# Number of characters in the new password that must not be present in the
+# old password.
+difok = 8
+#
+# Minimum acceptable size for the new password (plus one if
+# credits are not disabled which is the default). (See pam_cracklib manual.)
+# Cannot be set to lower value than 6.
+minlen = 15
+#
+# The maximum credit for having digits in the new password. If less than 0
+# it is the minimum number of digits in the new password.
+dcredit = -1
+#
+# The maximum credit for having uppercase characters in the new password.
+# If less than 0 it is the minimum number of uppercase characters in the new
+# password.
+ucredit = -1
+#
+# The maximum credit for having lowercase characters in the new password.
+# If less than 0 it is the minimum number of lowercase characters in the new
+# password.
+lcredit = -1
+#
+# The maximum credit for having other characters in the new password.
+# If less than 0 it is the minimum number of other characters in the new
+# password.
+ocredit = -1
+#
+# The minimum number of required classes of characters for the new
+# password (digits, uppercase, lowercase, others).
+minclass = 4
+#
+# The maximum number of allowed consecutive same characters in the new password.
+# The check is disabled if the value is 0.
+maxrepeat = 3
+#
+# The maximum number of allowed consecutive characters of the same class in the
+# new password.
+# The check is disabled if the value is 0.
+maxclassrepeat = 4
+#
+# Whether to check for the words from the passwd entry GECOS string of the user.
+# The check is enabled if the value is not 0.
+# gecoscheck = 0
+#
+# Whether to check for the words from the cracklib dictionary.
+# The check is enabled if the value is not 0.
+dictcheck = 1
+#
+# Whether to check if it contains the user name in some form.
+# The check is enabled if the value is not 0.
+# usercheck = 1
+#
+# Length of substrings from the username to check for in the password
+# The check is enabled if the value is greater than 0 and usercheck is enabled.
+# usersubstr = 0
+#
+# Whether the check is enforced by the PAM module and possibly other
+# applications.
+# The new password is rejected if it fails the check and the value is not 0.
+# enforcing = 1
+#
+# Path to the cracklib dictionaries. Default is to use the cracklib default.
+# dictpath =
+#
+# Prompt user at most N times before returning with error. The default is 1.
+retry = 3
+#
+# Enforces pwquality checks on the root user password.
+# Enabled if the option is present.
+# enforce_for_root
+#
+# Skip testing the password quality for users that are not present in the
+# /etc/passwd file.
+# Enabled if the option is present.
+# local_users_only
\ No newline at end of file
diff --git a/recipes-extended/libpwquality/libpwquality_1.%.bbappend b/recipes-extended/libpwquality/libpwquality_1.%.bbappend
new file mode 100644
index 000000000..c7ecb86c9
--- /dev/null
+++ b/recipes-extended/libpwquality/libpwquality_1.%.bbappend
@@ -0,0 +1,13 @@
+FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
+
+SRC_URI:append = "\
+ file://pwquality.conf \
+"
+
+do_install:append() {
+ install -d ${D}${sysconfdir}/security
+ install -m 644 ${WORKDIR}/pwquality.conf ${D}${sysconfdir}/security/pwquality.conf
+}
+
+FILES:${PN} += "${sysconfdir}/security/pwquality.conf"
+CONFFILES:${PN} += "${sysconfdir}/security/pwquality.conf"
diff --git a/recipes-extended/pam/libpam/security/faillock.conf b/recipes-extended/pam/libpam/security/faillock.conf
new file mode 100644
index 000000000..abec69ac7
--- /dev/null
+++ b/recipes-extended/pam/libpam/security/faillock.conf
@@ -0,0 +1,62 @@
+# Configuration for locking the user after multiple failed
+# authentication attempts.
+#
+# The directory where the user files with the failure records are kept.
+# The default is /var/run/faillock.
+# dir = /var/run/faillock
+#
+# Will log the user name into the system log if the user is not found.
+# Enabled if option is present.
+audit
+#
+# Don't print informative messages.
+# Enabled if option is present.
+silent
+#
+# Don't log informative messages via syslog.
+# Enabled if option is present.
+# no_log_info
+#
+# Only track failed user authentications attempts for local users
+# in /etc/passwd and ignore centralized (AD, IdM, LDAP, etc.) users.
+# The `faillock` command will also no longer track user failed
+# authentication attempts. Enabling this option will prevent a
+# double-lockout scenario where a user is locked out locally and
+# in the centralized mechanism.
+# Enabled if option is present.
+# local_users_only
+#
+# Deny access if the number of consecutive authentication failures
+# for this user during the recent interval exceeds n tries.
+# The default is 3.
+deny = 3
+#
+# The length of the interval during which the consecutive
+# authentication failures must happen for the user account
+# lock out is n seconds.
+# The default is 900 (15 minutes).
+fail_interval = 900
+#
+# The access will be re-enabled after n seconds after the lock out.
+# The value 0 has the same meaning as value `never` - the access
+# will not be re-enabled without resetting the faillock
+# entries by the `faillock` command.
+# The default is 600 (10 minutes).
+unlock_time = 0
+#
+# Root account can become locked as well as regular accounts.
+# Enabled if option is present.
+# even_deny_root
+#
+# This option implies the `even_deny_root` option.
+# Allow access after n seconds to root account after the
+# account is locked. In case the option is not specified
+# the value is the same as of the `unlock_time` option.
+# root_unlock_time = 900
+#
+# If a group name is specified with this option, members
+# of the group will be handled by this module the same as
+# the root account (the options `even_deny_root>` and
+# `root_unlock_time` will apply to them.
+# By default, the option is not set.
+# admin_group =
diff --git a/recipes-extended/pam/libpam_1.%.bbappend b/recipes-extended/pam/libpam_1.%.bbappend
index 9accc57ef..1a582a480 100644
--- a/recipes-extended/pam/libpam_1.%.bbappend
+++ b/recipes-extended/pam/libpam_1.%.bbappend
@@ -1 +1,23 @@
FILESEXTRAPATHS:prepend := "${THISDIR}/${BPN}:"
+
+SRC_URI += "\
+ file://security/faillock.conf \
+"
+
+do_install:append() {
+ install -m 644 ${WORKDIR}/security/faillock.conf ${D}${sysconfdir}/security/faillock.conf
+}
+
+pkg_postinst:pam-plugin-faillock:append() {
+ # enable faillock
+ sed -E -i 's/^(.+)success=1(.+)$/auth requisite pam_faillock.so preauth\n\1success=2\2\nauth [default=die] pam_faillock.so authfail/' "${sysconfdir}/pam.d/common-auth"
+ echo "auth sufficient pam_faillock.so authsucc" >> "${sysconfdir}/pam.d/common-auth"
+}
+
+pkg_prerm:pam-plugin-faillock:append() {
+ # disable faillock
+ sed -E -i '/pam_faillock.so/d' "${sysconfdir}/pam.d/common-auth"
+ sed -E -i 's/^(.+)success=2(.+)$/\1success=1\2/' "${sysconfdir}/pam.d/common-auth"
+}
+
+RCONFLICTS:pam-plugin-faillock:append = " ni-auth"
diff --git a/recipes-extended/sudo/sudo_1.%.bbappend b/recipes-extended/sudo/sudo_1.%.bbappend
new file mode 100644
index 000000000..a094dca7b
--- /dev/null
+++ b/recipes-extended/sudo/sudo_1.%.bbappend
@@ -0,0 +1,3 @@
+do_install:append() {
+ sed -i 's/^# \(%sudo ALL=(ALL:ALL) ALL\)$/\1/' ${D}${sysconfdir}/sudoers
+}
diff --git a/recipes-extended/tmux/files/tmux.conf b/recipes-extended/tmux/files/tmux.conf
new file mode 100644
index 000000000..76fa63182
--- /dev/null
+++ b/recipes-extended/tmux/files/tmux.conf
@@ -0,0 +1,3 @@
+set -g lock-command vlock
+bind X lock-session
+source-file -q /usr/share/tmux/conf.d/*
diff --git a/recipes-extended/tmux/tmux_3.%.bbappend b/recipes-extended/tmux/tmux_3.%.bbappend
new file mode 100644
index 000000000..bb808ea1a
--- /dev/null
+++ b/recipes-extended/tmux/tmux_3.%.bbappend
@@ -0,0 +1,15 @@
+FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
+
+SRC_URI += "\
+ file://tmux.conf \
+"
+
+do_install:append() {
+ install -d ${D}${sysconfdir}
+ install -m 644 ${WORKDIR}/tmux.conf ${D}${sysconfdir}/tmux.conf
+ install -d ${D}/usr/share/tmux/conf.d
+}
+
+FILES:${PN} += "${sysconfdir}/tmux.conf"
+CONFFILES:${PN} += "${sysconfdir}/tmux.conf"
+RDEPENDS:${PN}:append = " vlock"
diff --git a/recipes-ni/nilrt-snac/files/run-ptest b/recipes-ni/nilrt-snac/files/run-ptest
new file mode 100644
index 000000000..6b95c21d5
--- /dev/null
+++ b/recipes-ni/nilrt-snac/files/run-ptest
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+
+import os
+
+import pytest
+
+
+# nirtcfg is installed to a non-standard path
+os.environ["PATH"] = "/usr/local/natinst/bin:" + os.environ.get("PATH", "")
+
+pytest.main([
+ "--automake",
+ "/usr/lib/nilrt-snac/tests/integration",
+])
diff --git a/recipes-ni/nilrt-snac/nilrt-snac_git.bb b/recipes-ni/nilrt-snac/nilrt-snac_git.bb
new file mode 100644
index 000000000..07ceec18d
--- /dev/null
+++ b/recipes-ni/nilrt-snac/nilrt-snac_git.bb
@@ -0,0 +1,47 @@
+SUMMARY = "NILRT SNAC Configuration Tool"
+DESCRIPTION = "\
+A utility for admins to put a NILRT system into the SNAC configuration.\
+"
+HOMEPAGE = "https://github.com/ni/nilrt-snac"
+SECTION = "base"
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://LICENSE;md5=380df876633ca23587b9851600778cc0"
+
+
+SRC_URI = "\
+ git://github.com/ni/nilrt-snac;branch=master;protocol=https \
+ file://run-ptest \
+"
+
+SRCREV = "${AUTOREV}"
+PV = "0.1.1+git${SRCPV}"
+
+S = "${WORKDIR}/git"
+
+
+inherit ptest
+
+
+do_install() {
+ oe_runmake install \
+ DESTDIR=${D}
+}
+
+do_install_ptest() {
+ install -m 0755 ${WORKDIR}/run-ptest ${D}${PTEST_PATH}
+}
+
+
+RDEPENDS:${PN} = "\
+ bash \
+ opkg \
+ python3-core \
+"
+
+FILES:${PN}-ptest += "${libdir}/${PN}/tests/integration"
+RDEPENDS:${PN}-ptest += "\
+ bash \
+ python3-core \
+ python3-pytest \
+ python3-unittest-automake-output \
+"