1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
#!/usr/bin/env python3
#
# Copyright (C) 2020 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
import jinja2
from pwd import getpwall, getpwnam
from stat import S_IRUSR, S_IWUSR
from vyos.config import Config
from vyos.configdict import list_diff
from vyos import ConfigError
radius_config_file = "/etc/pam_radius_auth.conf"
radius_config_tmpl = """
# Automatically generated by VyOS
# RADIUS configuration file
{%- if server %}
# server[:port] shared_secret timeout (s) source_ip
{% for s in server %}
{%- if not s.disabled -%}
{{ s.address }}:{{ s.port }} {{ s.key }} {{ s.timeout }} {% if source_address -%}{{ source_address }}{% endif %}
{% endif %}
{%- endfor %}
priv-lvl 15
mapped_priv_user radius_priv_user
{% endif %}
"""
default_config_data = {
'server': [],
'source_address': '',
}
def get_local_users():
"""Returns list of dynamically allocated users (see Debian Policy Manual)"""
local_users = []
for p in getpwall():
username = p[0]
uid = getpwnam(username).pw_uid
if uid in range(1000, 29999):
if username not in ['radius_user', 'radius_priv_user']:
local_users.append(username)
return local_users
def get_config():
radius = default_config_data
conf = Config()
base_level = ['system', 'login', 'radius']
if not conf.exists(base_level):
return radius
conf.set_level(base_level)
if conf.exists(['source-address']):
radius['source_address'] = conf.return_value(['source-address'])
# Read in all RADIUS servers and store to list
for server in conf.list_nodes(['server']):
server_cfg = {
'address': server,
'disabled': False,
'key': '',
'port': '1812',
'timeout': '2'
}
conf.set_level(base_level + ['server', server])
# Check if RADIUS server was temporary disabled
if conf.exists(['disable']):
server_cfg['disabled'] = True
# RADIUS shared secret
if conf.exists(['key']):
server_cfg['key'] = conf.return_value(['key'])
# RADIUS authentication port
if conf.exists(['port']):
server_cfg['port'] = conf.return_value(['port'])
# RADIUS session timeout
if conf.exists(['timeout']):
server_cfg['timeout'] = conf.return_value(['timeout'])
# Append individual RADIUS server configuration to global server list
radius['server'].append(server_cfg)
return radius
def verify(radius):
# At lease one RADIUS server must not be disabled
if len(radius['server']) > 0:
fail = True
for server in radius['server']:
if not server['disabled']:
fail = False
if fail:
raise ConfigError('At least one RADIUS server must be active.')
return None
def generate(radius):
if len(radius['server']) > 0:
tmpl = jinja2.Template(radius_config_tmpl)
config_text = tmpl.render(radius)
with open(radius_config_file, 'w') as f:
f.write(config_text)
uid = getpwnam('root').pw_uid
gid = getpwnam('root').pw_gid
os.chown(radius_config_file, uid, gid)
os.chmod(radius_config_file, S_IRUSR | S_IWUSR)
else:
os.unlink(radius_config_file)
return None
def apply(radius):
if len(radius['server']) > 0:
try:
# Enable RADIUS in PAM
os.system("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --enable radius")
# Make NSS system aware of RADIUS, too
cmd = "sed -i -e \'/\smapname/b\' \
-e \'/^passwd:/s/\s\s*/&mapuid /\' \
-e \'/^passwd:.*#/s/#.*/mapname &/\' \
-e \'/^passwd:[^#]*$/s/$/ mapname &/\' \
-e \'/^group:.*#/s/#.*/ mapname &/\' \
-e \'/^group:[^#]*$/s/: */&mapname /\' \
/etc/nsswitch.conf"
os.system(cmd)
except Exception as e:
raise ConfigError('RADIUS configuration failed: {}'.format(e))
else:
try:
# Disable RADIUS in PAM
os.system("DEBIAN_FRONTEND=noninteractive pam-auth-update --package --remove radius")
cmd = "sed -i -e \'/^passwd:.*mapuid[ \t]/s/mapuid[ \t]//\' \
-e \'/^passwd:.*[ \t]mapname/s/[ \t]mapname//\' \
-e \'/^group:.*[ \t]mapname/s/[ \t]mapname//\' \
-e \'s/[ \t]*$//\' \
/etc/nsswitch.conf"
os.system(cmd)
except Exception as e:
raise ConfigError('Removing RADIUS configuration failed'.format(e))
return None
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
sys.exit(1)
|