@@ -10,12 +10,15 @@ import syslog
1010import signal
1111import re
1212import jinja2
13+ import json
1314import threading
1415from shutil import copy2
16+ from datetime import datetime
1517from sonic_py_common import device_info
1618from sonic_py_common .general import check_output_pipe
1719from swsscommon .swsscommon import ConfigDBConnector , DBConnector , Table , SonicDBConfig
1820from swsscommon import swsscommon
21+ from sonic_installer import bootloader
1922
2023# FILE
2124PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic"
@@ -65,12 +68,18 @@ RADIUS_SERVER_TIMEOUT_DEFAULT = "5"
6568RADIUS_SERVER_AUTH_TYPE_DEFAULT = "pap"
6669RADIUS_PAM_AUTH_CONF_DIR = "/etc/pam_radius_auth.d/"
6770
71+ # FIPS
72+ FIPS_CONFIG_FILE = '/etc/sonic/fips.json'
73+ OPENSSL_FIPS_CONFIG_FILE = '/etc/fips/fips_enable'
74+ DEFAULT_FIPS_RESTART_SERVICES = ['ssh' , 'telemetry.service' , 'restapi' ]
75+
6876# MISC Constants
6977CFG_DB = "CONFIG_DB"
7078STATE_DB = "STATE_DB"
7179HOSTCFGD_MAX_PRI = 10 # Used to enforce ordering b/w daemons under Hostcfgd
7280DEFAULT_SELECT_TIMEOUT = 1000
7381PORT_INIT_TIMEOUT_SEC = 180
82+ PROC_CMDLINE = '/proc/cmdline'
7483
7584
7685def safe_eval (val , default_value = False ):
@@ -115,6 +124,18 @@ def run_cmd_pipe(cmd0, cmd1, cmd2, log_err=True, raise_exception=False):
115124 if raise_exception :
116125 raise
117126
127+ def run_cmd_output (cmd , log_err = True , raise_exception = False ):
128+ output = ''
129+ try :
130+ output = subprocess .check_output (cmd )
131+ except Exception as err :
132+ if log_err :
133+ syslog .syslog (syslog .LOG_ERR , "{} - failed: return code - {}, output:\n {}"
134+ .format (err .cmd , err .returncode , err .output ))
135+ if raise_exception :
136+ raise
137+ return output
138+
118139
119140def is_true (val ):
120141 if val == 'True' or val == 'true' :
@@ -1766,7 +1787,6 @@ class RSyslogCfg(object):
17661787 self .cache ['config' ] = rsyslog_config
17671788 self .cache ['servers' ] = rsyslog_servers
17681789
1769-
17701790class DnsCfg :
17711791
17721792 def load (self , * args , ** kwargs ):
@@ -1775,6 +1795,100 @@ class DnsCfg:
17751795 def dns_update (self , * args , ** kwargs ):
17761796 run_cmd (['systemctl' , 'restart' , 'resolv-config' ], True , False )
17771797
1798+ class FipsCfg (object ):
1799+ """
1800+ FipsCfg Config Daemon
1801+ Handles the changes in FIPS table.
1802+ """
1803+
1804+ def __init__ (self , state_db_conn ):
1805+ self .enable = False
1806+ self .enforce = False
1807+ self .restart_services = DEFAULT_FIPS_RESTART_SERVICES
1808+ self .state_db_conn = state_db_conn
1809+
1810+ def read_config (self ):
1811+ if os .path .exists (FIPS_CONFIG_FILE ):
1812+ with open (FIPS_CONFIG_FILE ) as f :
1813+ conf = json .load (f )
1814+ self .restart_services = conf .get (RESTART_SERVICES_KEY , [])
1815+
1816+ with open (PROC_CMDLINE ) as f :
1817+ kernel_cmdline = f .read ().strip ().split (' ' )
1818+ self .cur_enforced = 'sonic_fips=1' in kernel_cmdline or 'fips=1' in kernel_cmdline
1819+
1820+ def load (self , data = {}):
1821+ common_config = data .get ('global' , {})
1822+ if not common_config :
1823+ syslog .syslog (syslog .LOG_INFO , f'FipsCfg: skipped the FIPS config, the FIPS setting is empty.' )
1824+ return
1825+ self .read_config ()
1826+ self .enforce = is_true (common_config .get ('enforce' , 'false' ))
1827+ self .enable = self .enforce or is_true (common_config .get ('enable' , 'false' ))
1828+ self .update ()
1829+
1830+ def fips_handler (self , data ):
1831+ self .load (data )
1832+
1833+ def update (self ):
1834+ syslog .syslog (syslog .LOG_DEBUG , f'FipsCfg: update fips option enable: { self .enable } , enforce: { self .enforce } .' )
1835+ self .update_enforce_config ()
1836+ self .update_noneenforce_config ()
1837+ self .state_db_conn .hset ('FIPS_STATS|state' , 'config_datetime' , datetime .utcnow ().isoformat ())
1838+ syslog .syslog (syslog .LOG_DEBUG , f'FipsCfg: update fips option complete.' )
1839+
1840+ def update_noneenforce_config (self ):
1841+ cur_fips_enabled = '0'
1842+ if os .path .exists (OPENSSL_FIPS_CONFIG_FILE ):
1843+ with open (OPENSSL_FIPS_CONFIG_FILE ) as f :
1844+ cur_fips_enabled = f .read ().strip ()
1845+
1846+ expected_fips_enabled = '0'
1847+ if self .enable :
1848+ expected_fips_enabled = '1'
1849+
1850+ # If the runtime config is not as expected, change the config
1851+ if cur_fips_enabled != expected_fips_enabled :
1852+ os .makedirs (os .path .dirname (OPENSSL_FIPS_CONFIG_FILE ), exist_ok = True )
1853+ with open (OPENSSL_FIPS_CONFIG_FILE , 'w' ) as f :
1854+ f .write (expected_fips_enabled )
1855+
1856+ self .restart ()
1857+
1858+ def restart (self ):
1859+ if self .cur_enforced :
1860+ syslog .syslog (syslog .LOG_INFO , f'FipsCfg: skipped to restart services, since FIPS enforced.' )
1861+ return
1862+
1863+ modified_time = datetime .utcfromtimestamp (0 )
1864+ if os .path .exists (OPENSSL_FIPS_CONFIG_FILE ):
1865+ modified_time = datetime .fromtimestamp (os .path .getmtime (OPENSSL_FIPS_CONFIG_FILE ))
1866+ timestamp = self .state_db_conn .hget ('FIPS_STATS|state' , 'config_datetime' )
1867+ if timestamp and datetime .fromisoformat (timestamp ).replace (tzinfo = None ) > modified_time .replace (tzinfo = None ):
1868+ syslog .syslog (syslog .LOG_INFO , f'FipsCfg: skipped to restart services, since the services have alread been restarted.' )
1869+ return
1870+
1871+ # Restart the services required and in the running state
1872+ output = run_cmd_output (['sudo' , 'systemctl' , '-t' , 'service' , '--state=running' , '--no-pager' , '-o' , 'json' ])
1873+ if not output :
1874+ return
1875+
1876+ services = [s ['unit' ] for s in json .loads (output )]
1877+ for service in self .restart_services :
1878+ if service in services or service + '.service' in services :
1879+ syslog .syslog (syslog .LOG_INFO , f'FipsCfg: restart service { service } .' )
1880+ run_cmd (['sudo' , 'systemctl' , 'restart' , service ])
1881+
1882+
1883+ def update_enforce_config (self ):
1884+ loader = bootloader .get_bootloader ()
1885+ image = loader .get_next_image ()
1886+ next_enforced = loader .get_fips (image )
1887+ if next_enforced == self .enforce :
1888+ syslog .syslog (syslog .LOG_INFO , f'FipsCfg: skipped to configure the enforce option { self .enforce } , since the config has already been set.' )
1889+ return
1890+ syslog .syslog (syslog .LOG_INFO , f'FipsCfg: update the FIPS enforce option { self .enforce } .' )
1891+ loader .set_fips (image , self .enforce )
17781892
17791893class HostConfigDaemon :
17801894 def __init__ (self ):
@@ -1835,6 +1949,9 @@ class HostConfigDaemon:
18351949 # Initialize DnsCfg
18361950 self .dnscfg = DnsCfg ()
18371951
1952+ # Initialize FipsCfg
1953+ self .fipscfg = FipsCfg (self .state_db_conn )
1954+
18381955 def load (self , init_data ):
18391956 features = init_data ['FEATURE' ]
18401957 aaa = init_data ['AAA' ]
@@ -1854,6 +1971,7 @@ class HostConfigDaemon:
18541971 syslog_cfg = init_data .get (swsscommon .CFG_SYSLOG_CONFIG_TABLE_NAME , {})
18551972 syslog_srv = init_data .get (swsscommon .CFG_SYSLOG_SERVER_TABLE_NAME , {})
18561973 dns = init_data .get ('DNS_NAMESERVER' , {})
1974+ fips_cfg = init_data .get ('FIPS' , {})
18571975
18581976 self .feature_handler .sync_state_field (features )
18591977 self .aaacfg .load (aaa , tacacs_global , tacacs_server , radius_global , radius_server )
@@ -1867,6 +1985,7 @@ class HostConfigDaemon:
18671985
18681986 self .rsyslogcfg .load (syslog_cfg , syslog_srv )
18691987 self .dnscfg .load (dns )
1988+ self .fipscfg .load (fips_cfg )
18701989
18711990 # Update AAA with the hostname
18721991 self .aaacfg .hostname_update (self .devmetacfg .hostname )
@@ -1990,6 +2109,11 @@ class HostConfigDaemon:
19902109 syslog .syslog (syslog .LOG_INFO , 'DNS nameserver handler...' )
19912110 self .dnscfg .dns_update (key , data )
19922111
2112+ def fips_config_handler (self , key , op , data ):
2113+ syslog .syslog (syslog .LOG_INFO , 'FIPS table handler...' )
2114+ data = self .config_db .get_table ("FIPS" )
2115+ self .fipscfg .fips_handler (data )
2116+
19932117 def wait_till_system_init_done (self ):
19942118 # No need to print the output in the log file so using the "--quiet"
19952119 # flag
@@ -2046,6 +2170,9 @@ class HostConfigDaemon:
20462170
20472171 self .config_db .subscribe ('DNS_NAMESERVER' , make_callback (self .dns_nameserver_handler ))
20482172
2173+ # Handle FIPS changes
2174+ self .config_db .subscribe ('FIPS' , make_callback (self .fips_config_handler ))
2175+
20492176 syslog .syslog (syslog .LOG_INFO ,
20502177 "Waiting for systemctl to finish initialization" )
20512178 self .wait_till_system_init_done ()
@@ -2055,7 +2182,6 @@ class HostConfigDaemon:
20552182 def start (self ):
20562183 self .config_db .listen (init_data_handler = self .load )
20572184
2058-
20592185def main ():
20602186 signal .signal (signal .SIGTERM , signal_handler )
20612187 signal .signal (signal .SIGINT , signal_handler )
0 commit comments