diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6c1be77b..af214110 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,13 +7,6 @@ trigger: - master - 202??? -resources: - repositories: - - repository: sonic-buildimage - type: github - name: sonic-net/sonic-buildimage - endpoint: build - variables: - name: BUILD_BRANCH ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: @@ -30,7 +23,6 @@ stages: variables: DIFF_COVER_CHECK_THRESHOLD: 80 DIFF_COVER_ENABLE: 'true' - DIFF_COVER_WORKING_DIRECTORY: $(System.DefaultWorkingDirectory)/sonic-host-services pool: vmImage: ubuntu-20.04 @@ -42,16 +34,6 @@ stages: clean: true submodules: recursive displayName: 'Checkout code' - - - checkout: sonic-buildimage - clean: true - displayName: 'Checkout code' - - - task: CopyFiles@2 - inputs: - SourceFolder: '$(System.DefaultWorkingDirectory)/sonic-buildimage/src/sonic-host-services-data/' - contents: '**' - targetFolder: $(System.DefaultWorkingDirectory)/sonic-host-services-data/ - task: DownloadPipelineArtifact@2 inputs: @@ -102,14 +84,12 @@ stages: displayName: "Install .NET CORE" - script: | - pushd sonic-host-services - python3 setup.py test displayName: 'Test Python 3' - task: PublishTestResults@2 inputs: - testResultsFiles: '$(System.DefaultWorkingDirectory)/sonic-host-services/test-results.xml' + testResultsFiles: '$(System.DefaultWorkingDirectory)/test-results.xml' testRunTitle: Python 3 failTaskOnFailedTests: true condition: succeededOrFailed() @@ -118,17 +98,16 @@ stages: - task: PublishCodeCoverageResults@1 inputs: codeCoverageTool: Cobertura - summaryFileLocation: '$(System.DefaultWorkingDirectory)/sonic-host-services/coverage.xml' - reportDirectory: '$(System.DefaultWorkingDirectory)/sonic-host-services/htmlcov/' + summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage.xml' + reportDirectory: '$(System.DefaultWorkingDirectory)/htmlcov/' displayName: 'Publish Python 3 test coverage' - script: | set -e - pushd sonic-host-services python3 setup.py bdist_wheel displayName: 'Build Python 3 wheel' - - publish: '$(System.DefaultWorkingDirectory)/sonic-host-services/dist/' + - publish: '$(System.DefaultWorkingDirectory)/dist/' artifact: wheels displayName: "Publish Python wheels" diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 00000000..b941ede4 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1,6 @@ +debian/*.debhelper +debian/debhelper-build-stamp +debian/sonic-host-services-data/ +sonic-host-services-data_*.buildinfo +sonic-host-services-data_*.changes +sonic-host-services-data_*.deb diff --git a/data/README.md b/data/README.md new file mode 100644 index 00000000..4a968ad8 --- /dev/null +++ b/data/README.md @@ -0,0 +1,19 @@ +# sonic-host-services-data +Data files required for SONiC host services + + +## To build + +``` +dpkg-buildpackage -rfakeroot -b -us -uc +``` + +## To clean + +``` +dpkg-buildpackage -rfakeroot -Tclean +``` + +--- + +See the [SONiC Website](https://sonicfoundation.dev/) for more information about the SONiC project. diff --git a/data/debian/changelog b/data/debian/changelog new file mode 100644 index 00000000..89e14bad --- /dev/null +++ b/data/debian/changelog @@ -0,0 +1,5 @@ +sonic-host-services-data (1.0-1) UNRELEASED; urgency=low + + * Initial release + + -- Joe LeVeque Tue, 20 Oct 2020 02:35:43 +0000 diff --git a/data/debian/compat b/data/debian/compat new file mode 100644 index 00000000..b4de3947 --- /dev/null +++ b/data/debian/compat @@ -0,0 +1 @@ +11 diff --git a/data/debian/control b/data/debian/control new file mode 100644 index 00000000..ebb495e3 --- /dev/null +++ b/data/debian/control @@ -0,0 +1,11 @@ +Source: sonic-host-services-data +Maintainer: Joe LeVeque +Section: misc +Priority: optional +Standards-Version: 0.1 +Build-Depends: debhelper (>=11) + +Package: sonic-host-services-data +Architecture: all +Depends: ${misc:Depends} +Description: Data files required for SONiC host services diff --git a/data/debian/copyright b/data/debian/copyright new file mode 100644 index 00000000..e69de29b diff --git a/data/debian/install b/data/debian/install new file mode 100644 index 00000000..91edbd1c --- /dev/null +++ b/data/debian/install @@ -0,0 +1,2 @@ +templates/*.j2 /usr/share/sonic/templates/ +org.sonic.hostservice.conf /etc/dbus-1/system.d diff --git a/data/debian/rules b/data/debian/rules new file mode 100755 index 00000000..47d26ccb --- /dev/null +++ b/data/debian/rules @@ -0,0 +1,24 @@ +#!/usr/bin/make -f + +ifeq (${ENABLE_HOST_SERVICE_ON_START}, y) + HOST_SERVICE_OPTS := --no-start +else + HOST_SERVICE_OPTS := --no-start --no-enable +endif + + +build: + +%: + dh $@ + +override_dh_installsystemd: + dh_installsystemd --no-start --name=caclmgrd + dh_installsystemd --no-start --name=hostcfgd + dh_installsystemd --no-start --name=featured + dh_installsystemd --no-start --name=aaastatsd + dh_installsystemd --no-start --name=procdockerstatsd + dh_installsystemd --no-start --name=determine-reboot-cause + dh_installsystemd --no-start --name=process-reboot-cause + dh_installsystemd $(HOST_SERVICE_OPTS) --name=sonic-hostservice + diff --git a/data/debian/sonic-host-services-data.aaastatsd.service b/data/debian/sonic-host-services-data.aaastatsd.service new file mode 100644 index 00000000..b93fe92c --- /dev/null +++ b/data/debian/sonic-host-services-data.aaastatsd.service @@ -0,0 +1,14 @@ +[Unit] +Description=AAA Statistics Collection daemon +Requires=hostcfgd.service +After=hostcfgd.service updategraph.service +BindsTo=sonic.target +After=sonic.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/aaastatsd +Restart=on-failure +RestartSec=10 +TimeoutStopSec=3 + diff --git a/data/debian/sonic-host-services-data.aaastatsd.timer b/data/debian/sonic-host-services-data.aaastatsd.timer new file mode 100644 index 00000000..8b6426db --- /dev/null +++ b/data/debian/sonic-host-services-data.aaastatsd.timer @@ -0,0 +1,12 @@ +[Unit] +Description=Delays aaastatsd daemon until SONiC has started +PartOf=aaastatsd.service + +[Timer] +OnUnitActiveSec=0 sec +OnBootSec=1min 30 sec +Unit=aaastatsd.service + +[Install] +WantedBy=timers.target sonic.target + diff --git a/data/debian/sonic-host-services-data.caclmgrd.service b/data/debian/sonic-host-services-data.caclmgrd.service new file mode 100644 index 00000000..e24ed10b --- /dev/null +++ b/data/debian/sonic-host-services-data.caclmgrd.service @@ -0,0 +1,15 @@ +[Unit] +Description=Control Plane ACL configuration daemon +Requires=updategraph.service +After=updategraph.service +BindsTo=sonic.target +After=sonic.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/caclmgrd +Restart=always +RestartSec=30 + +[Install] +WantedBy=sonic.target diff --git a/data/debian/sonic-host-services-data.determine-reboot-cause.service b/data/debian/sonic-host-services-data.determine-reboot-cause.service new file mode 100644 index 00000000..e834b933 --- /dev/null +++ b/data/debian/sonic-host-services-data.determine-reboot-cause.service @@ -0,0 +1,12 @@ +[Unit] +Description=Reboot cause determination service +Requires=rc-local.service database.service +After=rc-local.service database.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/local/bin/determine-reboot-cause + +[Install] +WantedBy=multi-user.target diff --git a/data/debian/sonic-host-services-data.featured.service b/data/debian/sonic-host-services-data.featured.service new file mode 100644 index 00000000..0913e945 --- /dev/null +++ b/data/debian/sonic-host-services-data.featured.service @@ -0,0 +1,10 @@ +[Unit] +Description=Feature configuration daemon +Requires=updategraph.service +After=updategraph.service +BindsTo=sonic.target +After=sonic.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/featured diff --git a/data/debian/sonic-host-services-data.featured.timer b/data/debian/sonic-host-services-data.featured.timer new file mode 100644 index 00000000..12fbbe10 --- /dev/null +++ b/data/debian/sonic-host-services-data.featured.timer @@ -0,0 +1,12 @@ +[Unit] +Description=Delays feature daemon until SONiC has started +PartOf=featured.service + +[Timer] +OnUnitActiveSec=0 sec +OnBootSec=1min 30 sec +Unit=featured.service + +[Install] +WantedBy=timers.target sonic.target + diff --git a/data/debian/sonic-host-services-data.hostcfgd.service b/data/debian/sonic-host-services-data.hostcfgd.service new file mode 100644 index 00000000..5e243452 --- /dev/null +++ b/data/debian/sonic-host-services-data.hostcfgd.service @@ -0,0 +1,11 @@ +[Unit] +Description=Host config enforcer daemon +Requires=updategraph.service +After=updategraph.service +BindsTo=sonic.target +After=sonic.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/hostcfgd + diff --git a/data/debian/sonic-host-services-data.hostcfgd.timer b/data/debian/sonic-host-services-data.hostcfgd.timer new file mode 100644 index 00000000..b45fd4b2 --- /dev/null +++ b/data/debian/sonic-host-services-data.hostcfgd.timer @@ -0,0 +1,12 @@ +[Unit] +Description=Delays hostcfgd daemon until SONiC has started +PartOf=hostcfgd.service + +[Timer] +OnUnitActiveSec=0 sec +OnBootSec=1min 30 sec +Unit=hostcfgd.service + +[Install] +WantedBy=timers.target sonic.target + diff --git a/data/debian/sonic-host-services-data.procdockerstatsd.service b/data/debian/sonic-host-services-data.procdockerstatsd.service new file mode 100644 index 00000000..68b9e61b --- /dev/null +++ b/data/debian/sonic-host-services-data.procdockerstatsd.service @@ -0,0 +1,14 @@ +[Unit] +Description=Process and docker CPU/memory utilization data export daemon +Requires=database.service updategraph.service +After=database.service updategraph.service +BindsTo=sonic.target +After=sonic.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/procdockerstatsd +Restart=always + +[Install] +WantedBy=sonic.target diff --git a/data/debian/sonic-host-services-data.process-reboot-cause.service b/data/debian/sonic-host-services-data.process-reboot-cause.service new file mode 100644 index 00000000..14af8868 --- /dev/null +++ b/data/debian/sonic-host-services-data.process-reboot-cause.service @@ -0,0 +1,8 @@ +[Unit] +Description=Retrieve the reboot cause from the history files and save them to StateDB +Requires=database.service determine-reboot-cause.service +After=database.service determine-reboot-cause.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/process-reboot-cause diff --git a/data/debian/sonic-host-services-data.process-reboot-cause.timer b/data/debian/sonic-host-services-data.process-reboot-cause.timer new file mode 100644 index 00000000..222c51a7 --- /dev/null +++ b/data/debian/sonic-host-services-data.process-reboot-cause.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Delays process-reboot-cause until network is stably connected + +[Timer] +OnBootSec=1min 30 sec +Unit=process-reboot-cause.service + +[Install] +WantedBy=timers.target diff --git a/data/debian/sonic-host-services-data.sonic-hostservice.service b/data/debian/sonic-host-services-data.sonic-hostservice.service new file mode 100644 index 00000000..799f3511 --- /dev/null +++ b/data/debian/sonic-host-services-data.sonic-hostservice.service @@ -0,0 +1,16 @@ +[Unit] +Description=SONiC Host Service + +[Service] +Type=dbus +BusName=org.SONiC.HostService + +ExecStart=/usr/bin/python3 -u /usr/local/bin/sonic-host-server + +Restart=on-failure +RestartSec=10 +TimeoutStopSec=3 + +[Install] +WantedBy=mgmt-framework.service telemetry.service + diff --git a/data/org.sonic.hostservice.conf b/data/org.sonic.hostservice.conf new file mode 100644 index 00000000..08599007 --- /dev/null +++ b/data/org.sonic.hostservice.conf @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/data/templates/common-auth-sonic.j2 b/data/templates/common-auth-sonic.j2 new file mode 100644 index 00000000..b20c9f4e --- /dev/null +++ b/data/templates/common-auth-sonic.j2 @@ -0,0 +1,83 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-auth- authentication settings common to all services +# This file is included from other service-specific PAM config files, +# and should contain a list of the authentication modules that define +# the central authentication scheme for use on the system +# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the +# traditional Unix authentication mechanisms. +# +# here are the per-package modules (the "Primary" block) + +{% if auth['login'] == 'local' %} +auth [success=1 default=ignore] pam_unix.so nullok try_first_pass + +{% elif auth['login'] == 'local,tacacs+' %} +auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass +{% for server in servers | sub(0, -1) %} +auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_tacplus.so server={{ server.ip }}:{{ server.tcp_port }} secret={{ server.passkey }} login={{ server.auth_type }} timeout={{ server.timeout }} {% if server.vrf %} vrf={{ server.vrf }} {% endif %} {{ 'source_ip=%s' % src_ip if src_ip }} try_first_pass +{% endfor %} +{% if servers | count %} +{% set last_server = servers | last %} +auth [success=1 default=ignore] pam_tacplus.so server={{ last_server.ip }}:{{ last_server.tcp_port }} secret={{ last_server.passkey }} login={{ last_server.auth_type }} timeout={{ last_server.timeout }} {% if last_server.vrf %} vrf={{ last_server.vrf }} {% endif %} {{ 'source_ip=%s' % src_ip if src_ip }} try_first_pass + +{% endif %} +{% elif auth['login'] == 'tacacs+' or auth['login'] == 'tacacs+,local' %} +{% for server in servers %} +auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_tacplus.so server={{ server.ip }}:{{ server.tcp_port }} secret={{ server.passkey }} login={{ server.auth_type }} timeout={{ server.timeout }} {%if server.vrf %} vrf={{ server.vrf }} {% endif %} {{ 'source_ip=%s' % src_ip if src_ip }} try_first_pass +{% endfor %} +auth [success=1 default=ignore] pam_unix.so nullok try_first_pass + +{% elif auth['login'] == 'local,radius' %} +auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass +# For the RADIUS servers, on success jump to the cacheing the MPL(Privilege) +{% for server in servers %} +auth [success={{ (servers | count) - loop.index0 }} new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_radius_auth.so conf=/etc/pam_radius_auth.d/{{ server.ip }}_{{ server.auth_port }}.conf privilege_level protocol={{ server.auth_type }} retry={{ server.retransmit }}{% if server.nas_ip is defined %} nas_ip_address={{ server.nas_ip }}{% endif %}{% if server.nas_id is defined %} client_id={{ server.nas_id }}{% endif %}{% if debug %} debug{% endif %}{% if trace %} trace{% endif %}{% if server.statistics %} statistics={{ server.ip }}{% endif %} try_first_pass +{% endfor %} +auth requisite pam_deny.so +# Cache MPL(Privilege) +auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius + +{% elif auth['login'] == 'radius,local' %} +# root user can only be authenticated locally. Jump to local. +{% if servers | count %} +auth [success={{ (servers | count) }} default=ignore] pam_succeed_if.so user = root +{% else %} +auth [success=ok default=ignore] pam_succeed_if.so user = root +{% endif %} +# For the RADIUS servers, on success jump to the cache the MPL(Privilege) +{% for server in servers %} +auth [success={{ (servers | count) + 1 - loop.index0 }} new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_radius_auth.so conf=/etc/pam_radius_auth.d/{{ server.ip }}_{{ server.auth_port }}.conf privilege_level protocol={{ server.auth_type }} retry={{ server.retransmit }}{% if server.nas_ip is defined %} nas_ip_address={{ server.nas_ip }}{% endif %}{% if server.nas_id is defined %} client_id={{ server.nas_id }}{% endif %}{% if debug %} debug{% endif %}{% if trace %} trace{% endif %}{% if server.statistics %} statistics={{ server.ip }}{% endif %} try_first_pass +{% endfor %} +# Local +auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass +auth requisite pam_deny.so +# Cache MPL(Privilege) +auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius + +{% elif auth['login'] == 'radius' %} +# root user can only be authenticated locally. Jump to local. +auth [success={{ (servers | count) + 2 }} default=ignore] pam_succeed_if.so user = root +# For the RADIUS servers, on success jump to the cache the MPL(Privilege) +{% for server in servers %} +auth [success={{ (servers | count) - loop.index0 }} new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_radius_auth.so conf=/etc/pam_radius_auth.d/{{ server.ip }}_{{ server.auth_port }}.conf privilege_level protocol={{ server.auth_type }} retry={{ server.retransmit }}{% if server.nas_ip is defined %} nas_ip_address={{ server.nas_ip }}{% endif %}{% if server.nas_id is defined %} client_id={{ server.nas_id }}{% endif %}{% if debug %} debug{% endif %}{% if trace %} trace{% endif %}{% if server.statistics %} statistics={{ server.ip }}{% endif %} try_first_pass +{% endfor %} +auth requisite pam_deny.so +# Cache MPL(Privilege) +auth [success=2 default=ignore] pam_exec.so /usr/sbin/cache_radius +# Local +auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass + +{% else %} +auth [success=1 default=ignore] pam_unix.so nullok try_first_pass + +{% endif %} +# +# here's the fallback if no module succeeds +auth requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +auth required pam_permit.so +# and here are more per-package modules (the "Additional" block) + diff --git a/data/templates/common-password.j2 b/data/templates/common-password.j2 new file mode 100644 index 00000000..91b46928 --- /dev/null +++ b/data/templates/common-password.j2 @@ -0,0 +1,43 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-password - password-related modules common to all services +# +# This file is included from other service-specific PAM config files, +# and should contain a list of modules that define the services to be +# used to change user passwords. The default is pam_unix. + +# Explanation of pam_unix options: +# The "yescrypt" option enables +#hashed passwords using the yescrypt algorithm, introduced in Debian +#11. Without this option, the default is Unix crypt. Prior releases +#used the option "sha512"; if a shadow password hash will be shared +#between Debian 11 and older releases replace "yescrypt" with "sha512" +#for compatibility . The "obscure" option replaces the old +#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage +#for other options. + +# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. +# To take advantage of this, it is recommended that you configure any +# local modules either before or after the default block, and use +# pam-auth-update to manage selection of other modules. See +# pam-auth-update(8) for details. + +# here are the per-package modules (the "Primary" block) + +{% if passw_policies %} +{% if passw_policies['state'] == 'enabled' %} +password requisite pam_pwquality.so retry=3 maxrepeat=0 {% if passw_policies['len_min'] %}minlen={{passw_policies['len_min']}}{% endif %} {% if passw_policies['upper_class'] %}ucredit=-1{% else %}ucredit=0{% endif %} {% if passw_policies['lower_class'] %}lcredit=-1{% else %}lcredit=0{% endif %} {% if passw_policies['digits_class'] %}dcredit=-1{% else %}dcredit=0{% endif %} {% if passw_policies['special_class'] %}ocredit=-1{% else %}ocredit=0{% endif %} {% if passw_policies['reject_user_passw_match'] %}reject_username{% endif %} enforce_for_root dictcheck=0 + +password required pam_pwhistory.so {% if passw_policies['history_cnt'] %}remember={{passw_policies['history_cnt']}}{% endif %} use_authtok enforce_for_root +{% endif %} +{% endif %} + +password [success=1 default=ignore] pam_unix.so obscure yescrypt +# here's the fallback if no module succeeds +password requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +password required pam_permit.so +# and here are more per-package modules (the "Additional" block) +# end of pam-auth-update config diff --git a/data/templates/limits.conf.j2 b/data/templates/limits.conf.j2 new file mode 100755 index 00000000..70ac9d8c --- /dev/null +++ b/data/templates/limits.conf.j2 @@ -0,0 +1,69 @@ +# /etc/security/limits.conf +# +# This file generate by j2 template file: src/sonic-host-services/data/templates/limits.conf.j2 +# +# Each line describes a limit for a user in the form: +# +# +# +# Where: +# can be: +# - a user name +# - a group name, with @group syntax +# - the wildcard *, for default entry +# - the wildcard %, can be also used with %group syntax, +# for maxlogin limit +# - NOTE: group and wildcard limits are not applied to root. +# To apply a limit to the root user, must be +# the literal username root. +# +# can have the two values: +# - "soft" for enforcing the soft limits +# - "hard" for enforcing hard limits +# +# can be one of the following: +# - core - limits the core file size (KB) +# - data - max data size (KB) +# - fsize - maximum filesize (KB) +# - memlock - max locked-in-memory address space (KB) +# - nofile - max number of open file descriptors +# - rss - max resident set size (KB) +# - stack - max stack size (KB) +# - cpu - max CPU time (MIN) +# - nproc - max number of processes +# - as - address space limit (KB) +# - maxlogins - max number of logins for this user +# - maxsyslogins - max number of logins on the system +# - priority - the priority to run user process with +# - locks - max number of file locks the user can hold +# - sigpending - max number of pending signals +# - msgqueue - max memory used by POSIX message queues (bytes) +# - nice - max nice priority allowed to raise to values: [-20, 19] +# - rtprio - max realtime priority +# - chroot - change root to directory (Debian-specific) +# +# +# is related with : +# All items support the values -1, unlimited or infinity indicating +# no limit, except for priority and nice. +# +# If a hard limit or soft limit of a resource is set to a valid value, +# but outside of the supported range of the local system, the system +# may reject the new limit or unexpected behavior may occur. If the +# control value required is used, the module will reject the login if +# a limit could not be set. +# +# +# + +# * soft core 0 +# root hard core 100000 +# * hard rss 10000 +# @student hard nproc 20 +# @faculty soft nproc 20 +# @faculty hard nproc 50 +# ftp hard nproc 0 +# ftp - chroot /ftp +# @student - maxlogins 4 + +# End of file diff --git a/data/templates/pam_limits.j2 b/data/templates/pam_limits.j2 new file mode 100755 index 00000000..4808c205 --- /dev/null +++ b/data/templates/pam_limits.j2 @@ -0,0 +1,12 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# This file generate by j2 template file: src/sonic-host-services/data/templates/pam_limits.j2 +# +# /etc/pam.d/pam-limits settings common to all services +# This file is included from other service-specific PAM config files, +# and should contain a list of the authentication modules that define +# the central authentication scheme for use on the system +# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the +# traditional Unix authentication mechanisms. +# +# here are the per-package modules (the "Primary" block) diff --git a/data/templates/pam_radius_auth.conf.j2 b/data/templates/pam_radius_auth.conf.j2 new file mode 100644 index 00000000..7d3c73e1 --- /dev/null +++ b/data/templates/pam_radius_auth.conf.j2 @@ -0,0 +1,3 @@ +# server[:port] shared_secret timeout(s) source_ip vrf +[{{ server.ip }}]:{{ server.auth_port }} {{ server.passkey }} {{ server.timeout }} {% if server.src_ip %} {{ server.src_ip }} {% endif %} {% if server.vrf %} {% if not server.src_ip %} - {% endif %} {{ server.vrf }}{% endif %} + diff --git a/data/templates/radius_nss.conf.j2 b/data/templates/radius_nss.conf.j2 new file mode 100644 index 00000000..a0da68d3 --- /dev/null +++ b/data/templates/radius_nss.conf.j2 @@ -0,0 +1,58 @@ +#THIS IS AN AUTO-GENERATED FILE +# Generated from: /usr/share/sonic/templates/radius_nss.conf.j2 +# RADIUS NSS Configuration File +# +# Debug: on|off|trace +# Default: off +# +# debug=on +{% if debug %} +debug=on +{% endif %} + +# +# User Privilege: +# Default: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell + +# Eg: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=7;pw_info=netops;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=operator;gid=100;group=docker;shell=/usr/bin/sonic-launch-shell +# + +# many_to_one: +# y: Map RADIUS users to one local user per privilege. +# n: Create local user account on first successful authentication. +# Default: n +# + +# Eg: +# many_to_one=y +# + +# unconfirmed_disallow: +# y: Do not allow unconfirmed users (users created before authentication) +# n: Allow unconfirmed users. +# Default: n + +# Eg: +# unconfirmed_disallow=y +# + +# unconfirmed_ageout: +# : Wait time before purging unconfirmed users +# Default: 600 +# + +# Eg: +# unconfirmed_ageout=900 +# + +# unconfirmed_regexp: +# : The RE to match the command line of processes for which the +# creation of unconfirmed users are to be allowed. +# Default: (.*: \[priv\])|(.*: \[accepted\]) +# where: is the unconfirmed user. +# diff --git a/data/templates/tacplus_nss.conf.j2 b/data/templates/tacplus_nss.conf.j2 new file mode 100644 index 00000000..812b47bf --- /dev/null +++ b/data/templates/tacplus_nss.conf.j2 @@ -0,0 +1,60 @@ +# Configuration for libnss-tacplus + +# debug - If you want to open debug log, set it on +# Default: off +# debug=on +{% if debug %} +debug=on +{% endif %} + +# local_accounting - If you want to local accounting, set it +# Default: None +# local_accounting +{% if local_accounting %} +local_accounting +{% endif %} + +# tacacs_accounting - If you want to tacacs+ accounting, set it +# Default: None +# tacacs_accounting +{% if tacacs_accounting %} +tacacs_accounting +{% endif %} + +# local_authorization - If you want to local authorization, set it +# Default: None +# local_authorization +{% if local_authorization %} +local_authorization +{% endif %} + +# tacacs_authorization - If you want to tacacs+ authorization, set it +# Default: None +# tacacs_authorization +{% if tacacs_authorization %} +tacacs_authorization +{% endif %} + +# src_ip - set source address of TACACS+ protocol packets +# Default: None (auto source ip address) +# src_ip=2.2.2.2 +{% if src_ip %} +src_ip={{ src_ip }} +{% endif %} + +# server - set ip address, tcp port, secret string and timeout for TACACS+ servers +# Default: None (no TACACS+ server) +# server=1.1.1.1:49,secret=test,timeout=3 +{% for server in servers %} +server={{ server.ip }}:{{ server.tcp_port }},secret={{ server.passkey }},timeout={{ server.timeout }}{% if server.vrf %},vrf={{ server.vrf }}{% endif %}{{''}} +{% endfor %} + +# user_priv - set the map between TACACS+ user privilege and local user's passwd +# Default: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/bin/bash +# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/bin/bash + +# many_to_one - create one local user for many TACACS+ users which has the same privilege +# Default: many_to_one=n +# many_to_one=y + diff --git a/scripts/caclmgrd b/scripts/caclmgrd index 1f044ad1..1b53c3a2 100755 --- a/scripts/caclmgrd +++ b/scripts/caclmgrd @@ -41,8 +41,10 @@ def _ip_prefix_in_key(key): """ return (isinstance(key, tuple)) -def get_ip_from_interface_table(table, intf_name): +def get_ipv4_networks_from_interface_table(table, intf_name): + + addresses = [] if table: for key, _ in table.items(): if not _ip_prefix_in_key(key): @@ -51,12 +53,11 @@ def get_ip_from_interface_table(table, intf_name): iface_name, iface_cidr = key if iface_name.startswith(intf_name): - ip_str = iface_cidr.split("/")[0] - ip_addr = ipaddress.ip_address(ip_str) - if isinstance(ip_addr, ipaddress.IPv4Address): - return ip_addr + ip_ntwrk = ipaddress.ip_network(iface_cidr, strict=False) + if isinstance(ip_ntwrk, ipaddress.IPv4Network): + addresses.append(ip_ntwrk) - return None + return addresses # ============================== Classes ============================== @@ -359,11 +360,24 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): if self.DualToR: loopback_table = config_db_connector.get_table(self.LOOPBACK_TABLE) loopback_name = 'Loopback3' - loopback_address = get_ip_from_interface_table(loopback_table, loopback_name) + loopback_networks = get_ipv4_networks_from_interface_table(loopback_table, loopback_name) + if len(loopback_networks) == 0: + self.log_warning("Loopback 3 IP not available from DualToR active-active config") + return fwd_dualtor_grpc_traffic_from_host_to_soc_cmds + + if not isinstance(loopback_networks[0], ipaddress.IPv4Network): + self.log_warning("Loopback 3 IP Network not available from DualToR active-active config") + return fwd_dualtor_grpc_traffic_from_host_to_soc_cmds + + loopback_address = loopback_networks[0].network_address vlan_name = 'Vlan' vlan_table = config_db_connector.get_table(self.VLAN_INTF_TABLE) + vlan_networks = get_ipv4_networks_from_interface_table(vlan_table, vlan_name) + + if len(vlan_networks) == 0: + self.log_warning("Vlan IP not available from DualToR active-active config") + return fwd_dualtor_grpc_traffic_from_host_to_soc_cmds - vlan_address = get_ip_from_interface_table(vlan_table, vlan_name) fwd_dualtor_grpc_traffic_from_host_to_soc_cmds.append(self.iptables_cmd_ns_prefix[namespace] + ['iptables', '-t', 'nat', '--flush', 'POSTROUTING']) @@ -373,11 +387,18 @@ class ControlPlaneAclManager(daemon_base.DaemonBase): for key in mux_table_keys: kvp = mux_table.get(key) if 'cable_type' in kvp and kvp['cable_type'] == 'active-active': - fwd_dualtor_grpc_traffic_from_host_to_soc_cmds.append(self.iptables_cmd_ns_prefix[namespace] + - ['iptables', '-t', 'nat', '-A', 'POSTROUTING', '--destination', kvp['soc_ipv4'], '--source', vlan_address, '-j', 'SNAT', '--to-source', loopback_address]) + soc_ipv4_str = kvp['soc_ipv4'].split("/")[0] + soc_ipv4_addr = ipaddress.ip_address(soc_ipv4_str) + for ip_network in vlan_networks: + # Only add the vlan source IP specific soc IP address to IPtables + if soc_ipv4_addr in ip_network: + vlan_address = ip_network.network_address + fwd_dualtor_grpc_traffic_from_host_to_soc_cmds.append(self.iptables_cmd_ns_prefix[namespace] + + ['iptables', '-t', 'nat', '-A', 'POSTROUTING', '--destination', str(soc_ipv4_addr), '--source', str(vlan_address), '-j', 'SNAT', '--to-source', str(loopback_address)]) return fwd_dualtor_grpc_traffic_from_host_to_soc_cmds + def generate_fwd_traffic_from_namespace_to_host_commands(self, namespace, acl_source_ip_map): """ The below SNAT and DNAT rules are added in asic namespace in multi-ASIC platforms. It helps to forward request coming diff --git a/setup.py b/setup.py index 1a9b51b8..51458f74 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,15 @@ 'sonic-py-common', 'deepdiff==6.2.2' ], + extras_require = { + "testing": [ + 'parameterized', + 'pytest', + 'pyfakefs', + 'sonic-py-common', + 'deepdiff==6.2.2' + ] + }, classifiers = [ 'Development Status :: 3 - Alpha', 'Environment :: Console', diff --git a/tests/caclmgrd/caclmgrd_soc_rules_test.py b/tests/caclmgrd/caclmgrd_soc_rules_test.py index 5f5ee4d8..ef40292a 100644 --- a/tests/caclmgrd/caclmgrd_soc_rules_test.py +++ b/tests/caclmgrd/caclmgrd_soc_rules_test.py @@ -4,11 +4,11 @@ from parameterized import parameterized from sonic_py_common.general import load_module_from_source -from ipaddress import IPv4Address +from ipaddress import IPv4Address, IPv4Network from unittest import TestCase, mock from pyfakefs.fake_filesystem_unittest import patchfs -from .test_soc_rules_vectors import CACLMGRD_SOC_TEST_VECTOR +from .test_soc_rules_vectors import CACLMGRD_SOC_TEST_VECTOR, CACLMGRD_SOC_TEST_VECTOR_EMPTY from tests.common.mock_configdb import MockConfigDb from unittest.mock import MagicMock, patch @@ -29,7 +29,7 @@ def setUp(self): @parameterized.expand(CACLMGRD_SOC_TEST_VECTOR) @patchfs - @patch('caclmgrd.get_ip_from_interface_table', MagicMock(return_value="10.10.10.10")) + @patch('caclmgrd.get_ipv4_networks_from_interface_table', MagicMock(return_value=[IPv4Network('10.10.10.18/24', strict=False), IPv4Network('10.10.11.18/24', strict=False)])) def test_caclmgrd_soc(self, test_name, test_data, fs): if not os.path.exists(DBCONFIG_PATH): fs.create_file(DBCONFIG_PATH) # fake database_config.json @@ -51,11 +51,62 @@ def test_caclmgrd_soc(self, test_name, test_data, fs): caclmgrd_daemon.update_control_plane_nat_acls('', {}, MockConfigDb()) mocked_subprocess.Popen.assert_has_calls(test_data["expected_subprocess_calls"], any_order=True) - def test_get_ip_from_interface_table(self): + + @parameterized.expand(CACLMGRD_SOC_TEST_VECTOR_EMPTY) + @patchfs + @patch('caclmgrd.get_ipv4_networks_from_interface_table', MagicMock(return_value=[])) + def test_caclmgrd_soc_no_ips(self, test_name, test_data, fs): + if not os.path.exists(DBCONFIG_PATH): + fs.create_file(DBCONFIG_PATH) # fake database_config.json + + MockConfigDb.set_config_db(test_data["config_db"]) + + with mock.patch("caclmgrd.ControlPlaneAclManager.run_commands_pipe", return_value='sonic'): + with mock.patch("caclmgrd.subprocess") as mocked_subprocess: + popen_mock = mock.Mock() + popen_attrs = test_data["popen_attributes"] + popen_mock.configure_mock(**popen_attrs) + mocked_subprocess.Popen.return_value = popen_mock + mocked_subprocess.PIPE = -1 + + call_rc = test_data["call_rc"] + mocked_subprocess.call.return_value = call_rc + + caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd") + caclmgrd_daemon.update_control_plane_nat_acls('', {}, MockConfigDb()) + mocked_subprocess.Popen.assert_has_calls(test_data["expected_subprocess_calls"], any_order=True) + + + @parameterized.expand(CACLMGRD_SOC_TEST_VECTOR_EMPTY) + @patchfs + @patch('caclmgrd.get_ipv4_networks_from_interface_table', MagicMock(return_value=['10.10.10.10'])) + def test_caclmgrd_soc_ip_string(self, test_name, test_data, fs): + if not os.path.exists(DBCONFIG_PATH): + fs.create_file(DBCONFIG_PATH) # fake database_config.json + + MockConfigDb.set_config_db(test_data["config_db"]) + + with mock.patch("caclmgrd.ControlPlaneAclManager.run_commands_pipe", return_value='sonic'): + with mock.patch("caclmgrd.subprocess") as mocked_subprocess: + popen_mock = mock.Mock() + popen_attrs = test_data["popen_attributes"] + popen_mock.configure_mock(**popen_attrs) + mocked_subprocess.Popen.return_value = popen_mock + mocked_subprocess.PIPE = -1 + + call_rc = test_data["call_rc"] + mocked_subprocess.call.return_value = call_rc + + caclmgrd_daemon = self.caclmgrd.ControlPlaneAclManager("caclmgrd") + caclmgrd_daemon.update_control_plane_nat_acls('', {}, MockConfigDb()) + mocked_subprocess.Popen.assert_has_calls(test_data["expected_subprocess_calls"], any_order=True) + + + def test_get_ipv4_networks_from_interface_table(self): if not os.path.exists(DBCONFIG_PATH): fs.create_file(DBCONFIG_PATH) # fake database_config.json table = {("Vlan1000","10.10.10.1/32"): "val"} - ip_addr = self.caclmgrd.get_ip_from_interface_table(table, "Vlan") + ip_addr = self.caclmgrd.get_ipv4_networks_from_interface_table(table, "Vlan") - assert (ip_addr == IPv4Address('10.10.10.1')) + assert (ip_addr == [IPv4Network('10.10.10.1/32')]) diff --git a/tests/caclmgrd/test_soc_rules_vectors.py b/tests/caclmgrd/test_soc_rules_vectors.py index c3b871c7..b339a32d 100644 --- a/tests/caclmgrd/test_soc_rules_vectors.py +++ b/tests/caclmgrd/test_soc_rules_vectors.py @@ -18,11 +18,11 @@ "MUX_CABLE": { "Ethernet4": { "cable_type": "active-active", - "soc_ipv4": "192.168.1.0/32", + "soc_ipv4": "10.10.11.7/32", } }, "VLAN_INTERFACE": { - "Vlan1000|10.10.2.2/23": { + "Vlan1000|10.10.10.3/24": { "NULL": "NULL", } }, @@ -35,7 +35,7 @@ }, }, "expected_subprocess_calls": [ - call(['iptables', '-t', 'nat', '-A', 'POSTROUTING', '--destination', '192.168.1.0/32', '--source', '10.10.10.10', '-j', 'SNAT', '--to-source', '10.10.10.10'], universal_newlines=True, stdout=-1) + call(['iptables', '-t', 'nat', '-A', 'POSTROUTING', '--destination', '10.10.11.7', '--source', '10.10.11.0', '-j', 'SNAT', '--to-source', '10.10.10.0'], universal_newlines=True, stdout=-1) ], "popen_attributes": { 'communicate.return_value': ('output', 'error'), @@ -44,3 +44,43 @@ } ] ] + + +CACLMGRD_SOC_TEST_VECTOR_EMPTY = [ + [ + "SOC_SESSION_TEST", + { + "config_db": { + "DEVICE_METADATA": { + "localhost": { + "subtype": "DualToR", + "type": "ToRRouter", + } + }, + "MUX_CABLE": { + "Ethernet4": { + "cable_type": "active-active", + "soc_ipv4": "10.10.11.7/32", + } + }, + "VLAN_INTERFACE": { + "Vlan1000|10.10.10.3/24": { + "NULL": "NULL", + } + }, + "LOOPBACK_INTERFACE": { + "Loopback3|10.10.10.10/32": { + "NULL": "NULL", + } + }, + "FEATURE": { + }, + }, + "expected_subprocess_calls": [], + "popen_attributes": { + 'communicate.return_value': ('output', 'error'), + }, + "call_rc": 0, + } + ] +] diff --git a/tests/hostcfgd/hostcfgd_passwh_test.py b/tests/hostcfgd/hostcfgd_passwh_test.py index 8cf08034..67dbf16b 100755 --- a/tests/hostcfgd/hostcfgd_passwh_test.py +++ b/tests/hostcfgd/hostcfgd_passwh_test.py @@ -16,8 +16,7 @@ test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) modules_path = os.path.dirname(test_path) scripts_path = os.path.join(modules_path, "scripts") -src_path = os.path.dirname(modules_path) -templates_path = os.path.join(src_path, "sonic-host-services-data/templates") +templates_path = os.path.join(modules_path, "data/templates") output_path = os.path.join(test_path, "hostcfgd/output") sample_output_path = os.path.join(test_path, "hostcfgd/sample_output") sys.path.insert(0, modules_path) @@ -123,7 +122,7 @@ def check_config(self, test_name, test_data, config_name): if not match: for name in files_to_compare: diff_output += self.run_diff( sop_path + "/" + name,\ - op_path + "/" + name).decode('utf-8') + op_path + "/" + name) self.assertTrue(len(diff_output) == 0, diff_output) diff --git a/tests/hostcfgd/hostcfgd_radius_test.py b/tests/hostcfgd/hostcfgd_radius_test.py index f45f2415..d2930aa9 100644 --- a/tests/hostcfgd/hostcfgd_radius_test.py +++ b/tests/hostcfgd/hostcfgd_radius_test.py @@ -16,8 +16,7 @@ test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) modules_path = os.path.dirname(test_path) scripts_path = os.path.join(modules_path, "scripts") -src_path = os.path.dirname(modules_path) -templates_path = os.path.join(src_path, "sonic-host-services-data/templates") +templates_path = os.path.join(modules_path, "data/templates") output_path = os.path.join(test_path, "hostcfgd/output") sample_output_path = os.path.join(test_path, "hostcfgd/sample_output") sys.path.insert(0, modules_path) diff --git a/tests/hostcfgd/hostcfgd_rsyslog_test.py b/tests/hostcfgd/hostcfgd_rsyslog_test.py index b9a84bca..a5ef3497 100755 --- a/tests/hostcfgd/hostcfgd_rsyslog_test.py +++ b/tests/hostcfgd/hostcfgd_rsyslog_test.py @@ -15,8 +15,7 @@ test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) modules_path = os.path.dirname(test_path) scripts_path = os.path.join(modules_path, "scripts") -src_path = os.path.dirname(modules_path) -templates_path = os.path.join(src_path, "sonic-host-services-data/templates") +templates_path = os.path.join(modules_path, "data/templates") output_path = os.path.join(test_path, "hostcfgd/output") sample_output_path = os.path.join(test_path, "hostcfgd/sample_output") sys.path.insert(0, modules_path) diff --git a/tests/hostcfgd/hostcfgd_ssh_server_test.py b/tests/hostcfgd/hostcfgd_ssh_server_test.py index aef1712b..2cfa8d13 100755 --- a/tests/hostcfgd/hostcfgd_ssh_server_test.py +++ b/tests/hostcfgd/hostcfgd_ssh_server_test.py @@ -15,7 +15,6 @@ test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) modules_path = os.path.dirname(test_path) scripts_path = os.path.join(modules_path, "scripts") -src_path = os.path.dirname(modules_path) output_path = os.path.join(test_path, "hostcfgd/output") sample_output_path = os.path.join(test_path, "hostcfgd/sample_output") sys.path.insert(0, modules_path) diff --git a/tests/hostcfgd/hostcfgd_tacacs_test.py b/tests/hostcfgd/hostcfgd_tacacs_test.py index 18d6f1a7..0577e61f 100644 --- a/tests/hostcfgd/hostcfgd_tacacs_test.py +++ b/tests/hostcfgd/hostcfgd_tacacs_test.py @@ -15,8 +15,7 @@ test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) modules_path = os.path.dirname(test_path) scripts_path = os.path.join(modules_path, "scripts") -src_path = os.path.dirname(modules_path) -templates_path = os.path.join(src_path, "sonic-host-services-data/templates") +templates_path = os.path.join(modules_path, "data/templates") output_path = os.path.join(test_path, "hostcfgd/output") sample_output_path = os.path.join(test_path, "hostcfgd/sample_output") sys.path.insert(0, modules_path) diff --git a/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password b/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password index 84107472..f5bbaf10 100644 --- a/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password +++ b/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password @@ -24,7 +24,7 @@ # here are the per-package modules (the "Primary" block) -password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 ucredit=0 lcredit=0 dcredit=-1 ocredit=0 enforce_for_root +password requisite pam_pwquality.so retry=3 maxrepeat=0 minlen=8 ucredit=0 lcredit=0 dcredit=-1 ocredit=0 enforce_for_root dictcheck=0 password required pam_pwhistory.so remember=0 use_authtok enforce_for_root diff --git a/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password b/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password index a66c1b1a..7ce952b7 100644 --- a/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password +++ b/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password @@ -24,7 +24,7 @@ # here are the per-package modules (the "Primary" block) -password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 reject_username enforce_for_root +password requisite pam_pwquality.so retry=3 maxrepeat=0 minlen=8 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 reject_username enforce_for_root dictcheck=0 password required pam_pwhistory.so remember=10 use_authtok enforce_for_root