1 |
|
# -*- coding: utf-8 -*- |
2 |
|
|
3 |
|
# Copyright 2020 TATA ELXSI |
4 |
|
# |
5 |
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may |
6 |
|
# not use this file except in compliance with the License. You may obtain |
7 |
|
# a copy of the License at |
8 |
|
# |
9 |
|
# http://www.apache.org/licenses/LICENSE-2.0 |
10 |
|
# |
11 |
|
# Unless required by applicable law or agreed to in writing, software |
12 |
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
13 |
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
14 |
|
# License for the specific language governing permissions and limitations |
15 |
|
# under the License. |
16 |
|
# |
17 |
|
# For those usages not covered by the Apache License, Version 2.0 please |
18 |
|
# contact: saikiran.k@tataelxsi.co.in |
19 |
|
## |
20 |
|
|
21 |
|
|
22 |
1 |
""" |
23 |
|
AuthconnTacacs implements implements the connector for TACACS. |
24 |
|
Leverages AuthconnInternal for token lifecycle management and the RBAC model. |
25 |
|
|
26 |
|
When NBI bootstraps, it tries to create admin user with admin role associated to admin project. |
27 |
|
Hence, the TACACS server should contain admin user. |
28 |
|
""" |
29 |
|
|
30 |
1 |
__author__ = "K Sai Kiran <saikiran.k@tataelxsi.co.in>" |
31 |
1 |
__date__ = "$11-Nov-2020 11:04:00$" |
32 |
|
|
33 |
|
|
34 |
1 |
from osm_nbi.authconn import Authconn, AuthException |
35 |
1 |
from osm_nbi.authconn_internal import AuthconnInternal |
36 |
1 |
from osm_nbi.base_topic import BaseTopic |
37 |
|
|
38 |
1 |
import logging |
39 |
1 |
from time import time |
40 |
1 |
from http import HTTPStatus |
41 |
|
|
42 |
|
# TACACS+ Library |
43 |
1 |
from tacacs_plus.client import TACACSClient |
44 |
|
|
45 |
|
|
46 |
1 |
class AuthconnTacacs(AuthconnInternal): |
47 |
1 |
token_time_window = 2 |
48 |
1 |
token_delay = 1 |
49 |
|
|
50 |
1 |
tacacs_def_port = 49 |
51 |
1 |
tacacs_def_timeout = 10 |
52 |
1 |
users_collection = "users_tacacs" |
53 |
1 |
roles_collection = "roles_tacacs" |
54 |
1 |
projects_collection = "projects_tacacs" |
55 |
1 |
tokens_collection = "tokens_tacacs" |
56 |
|
|
57 |
1 |
def __init__(self, config, db, role_permissions): |
58 |
|
""" |
59 |
|
Constructor to initialize db and TACACS server attributes to members. |
60 |
|
""" |
61 |
0 |
Authconn.__init__(self, config, db, role_permissions) |
62 |
0 |
self.logger = logging.getLogger("nbi.authenticator.tacacs") |
63 |
0 |
self.db = db |
64 |
0 |
self.tacacs_host = config["tacacs_host"] |
65 |
0 |
self.tacacs_secret = config["tacacs_secret"] |
66 |
0 |
self.tacacs_port = config["tacacs_port"] if config.get("tacacs_port") else self.tacacs_def_port |
67 |
0 |
self.tacacs_timeout = config["tacacs_timeout"] if config.get("tacacs_timeout") else self.tacacs_def_timeout |
68 |
0 |
self.tacacs_cli = TACACSClient(self.tacacs_host, self.tacacs_port, self.tacacs_secret, |
69 |
|
self.tacacs_timeout) |
70 |
|
|
71 |
1 |
def validate_user(self, user, password): |
72 |
|
""" |
73 |
|
""" |
74 |
0 |
now = time() |
75 |
0 |
try: |
76 |
0 |
tacacs_authen = self.tacacs_cli.authenticate(user, password) |
77 |
0 |
except Exception as e: |
78 |
0 |
raise AuthException("TACACS server error: {}".format(e), http_code=HTTPStatus.UNAUTHORIZED) |
79 |
0 |
user_content = None |
80 |
0 |
user_rows = self.db.get_list(self.users_collection, {BaseTopic.id_field("users", user): user}) |
81 |
0 |
if not tacacs_authen.valid: |
82 |
0 |
if user_rows: |
83 |
|
# To remove TACACS stale user from system. |
84 |
0 |
self.delete_user(user_rows[0][BaseTopic.id_field("users", user)]) |
85 |
0 |
return user_content |
86 |
0 |
if user_rows: |
87 |
0 |
user_content = user_rows[0] |
88 |
|
else: |
89 |
0 |
new_user = {'username': user, |
90 |
|
'password': password, |
91 |
|
'_admin': { |
92 |
|
'created': now, |
93 |
|
'modified': now |
94 |
|
}, |
95 |
|
'project_role_mappings': [] |
96 |
|
} |
97 |
0 |
user_content = self.create_user(new_user) |
98 |
0 |
return user_content |
99 |
|
|
100 |
1 |
def create_user(self, user_info): |
101 |
|
""" |
102 |
|
Validates user credentials in TACACS and add user. |
103 |
|
|
104 |
|
:param user_info: Full user information in dict. |
105 |
|
:return: returns username and id if credentails are valid. Otherwise, raise exception |
106 |
|
""" |
107 |
0 |
BaseTopic.format_on_new(user_info, make_public=False) |
108 |
0 |
try: |
109 |
0 |
authen = self.tacacs_cli.authenticate(user_info["username"], user_info["password"]) |
110 |
0 |
if authen.valid: |
111 |
0 |
user_info.pop("password") |
112 |
0 |
self.db.create(self.users_collection, user_info) |
113 |
|
else: |
114 |
0 |
raise AuthException("TACACS server error: Invalid credentials", http_code=HTTPStatus.FORBIDDEN) |
115 |
0 |
except Exception as e: |
116 |
0 |
raise AuthException("TACACS server error: {}".format(e), http_code=HTTPStatus.BAD_REQUEST) |
117 |
0 |
return {"username": user_info["username"], "_id": user_info["_id"]} |
118 |
|
|
119 |
1 |
def update_user(self, user_info): |
120 |
|
""" |
121 |
|
Updates user information, in particular for add/remove of project and role mappings. |
122 |
|
Does not allow change of username or password. |
123 |
|
|
124 |
|
:param user_info: Full user information in dict. |
125 |
|
:return: returns None for successful add/remove of project and role map. |
126 |
|
""" |
127 |
0 |
if(user_info.get("username")): |
128 |
0 |
raise AuthException("Can not update username of this user", http_code=HTTPStatus.FORBIDDEN) |
129 |
0 |
if(user_info.get("password")): |
130 |
0 |
raise AuthException("Can not update password of this user", http_code=HTTPStatus.FORBIDDEN) |
131 |
0 |
super(AuthconnTacacs, self).update_user(user_info) |