blob: 9c19794edecc9d7990fc8c5353fdba84a28ff2d1 [file] [log] [blame]
Khen Nursimulu3869d8d2016-11-28 20:44:28 -05001#
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -05002# Copyright 2017 the original author or authors.
Khen Nursimulu3869d8d2016-11-28 20:44:28 -05003#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import structlog
18import sys
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050019import os
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050020from twisted.conch import avatar
21from twisted.cred import portal
22from twisted.conch.checkers import SSHPublicKeyChecker, InMemorySSHKeyDB
23from twisted.conch.ssh import factory, userauth, connection, keys, session
24from twisted.conch.ssh.transport import SSHServerTransport
25
26from twisted.cred.checkers import FilePasswordDB
27from twisted.internet import reactor
28from twisted.internet.defer import Deferred, inlineCallbacks
29# from twisted.python import log as logp
30from zope.interface import implementer
Khen Nursimulua7b842a2016-12-03 23:28:42 -050031from session.nc_protocol_handler import NetconfProtocolHandler
32from session.nc_connection import NetconfConnection
33from session.session_mgr import get_session_manager_instance
34from constants import Constants as C
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -050035from capabilities import Capabilities
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050036
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050037dir_path = os.path.dirname(os.path.realpath(__file__))
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050038
39# logp.startLogging(sys.stderr)
40
41log = structlog.get_logger()
42
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050043
44# @implementer(conchinterfaces.ISession)
45class NetconfAvatar(avatar.ConchUser):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050046 def __init__(self, username, nc_server, grpc_client):
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050047 avatar.ConchUser.__init__(self)
48 self.username = username
49 self.nc_server = nc_server
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050050 self.grpc_client = grpc_client
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050051 self.channelLookup.update({'session': session.SSHSession})
52 self.subsystemLookup.update(
53 {b"netconf": NetconfConnection})
54
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050055 def get_grpc_client(self):
56 return self.grpc_client
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050057
58 def get_nc_server(self):
59 return self.nc_server
60
Khen Nursimulua7b842a2016-12-03 23:28:42 -050061 def get_user(self):
62 return self.username
63
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050064 def logout(self):
65 log.info('netconf-avatar-logout', username=self.username)
66
67
68@implementer(portal.IRealm)
69class NetconfRealm(object):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050070 def __init__(self, nc_server, grpc_client):
71 self.grpc_client = grpc_client
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050072 self.nc_server = nc_server
73
74 def requestAvatar(self, avatarId, mind, *interfaces):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050075 user = NetconfAvatar(avatarId, self.nc_server, self.grpc_client)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050076 return interfaces[0], user, user.logout
77
78
79class NCServer(factory.SSHFactory):
80 #
81 services = {
82 'ssh-userauth': userauth.SSHUserAuthServer,
83 'ssh-connection': connection.SSHConnection
84 }
85
86 def __init__(self,
87 netconf_port,
88 server_private_key_file,
89 server_public_key_file,
90 client_public_keys_file,
91 client_passwords_file,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050092 grpc_client):
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050093
94 self.netconf_port = netconf_port
95 self.server_private_key_file = server_private_key_file
96 self.server_public_key_file = server_public_key_file
97 self.client_public_keys_file = client_public_keys_file
98 self.client_passwords_file = client_passwords_file
Khen Nursimulua7b842a2016-12-03 23:28:42 -050099 self.session_mgr = get_session_manager_instance()
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500100 self.grpc_client = grpc_client
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500101 self.capabilities = Capabilities()
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500102 self.connector = None
103 self.nc_client_map = {}
104 self.running = False
105 self.exiting = False
106
107 def start(self):
108 log.debug('starting')
109 if self.running:
110 return
111 self.running = True
112 reactor.callLater(0, self.start_ssh_server)
113 log.info('started')
114 return self
115
116 def stop(self):
117 log.debug('stopping')
118 self.exiting = True
119 self.connector.disconnect()
120 self.d_stopped.callback(None)
121 log.info('stopped')
122
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500123 def set_capabilities(self):
124 yang_schemas = self.grpc_client.yang_schemas
125 if not yang_schemas:
126 log.error('no-yang-schema')
127 return
128 self.capabilities.set_server_capabilities(yang_schemas)
129 self.capabilities.set_schema_dir(self.grpc_client.work_dir)
130
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500131 def reload_capabilities(self):
132 # TODO: Called when there is a reconnect to voltha
133 # If there are new device types then the new
134 # capabilities will be exposed for subsequent client connections to use
135 pass
136
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500137 def client_disconnected(self, result, handler, reason):
138 assert isinstance(handler, NetconfProtocolHandler)
139
140 log.info('client-disconnected', reason=reason)
141
142 # For now just nullify the handler
143 handler.close()
144
145 def client_connected(self, client_conn):
146 assert isinstance(client_conn, NetconfConnection)
147 log.info('client-connected')
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500148
149 #create a session
150 session = self.session_mgr.create_session(client_conn.avatar.get_user())
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500151 handler = NetconfProtocolHandler(self, client_conn,
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500152 session, self.grpc_client,
153 self.capabilities)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500154 client_conn.proto_handler = handler
155 reactor.callLater(0, handler.start)
156
157 def setup_secure_access(self):
158 try:
159 from twisted.cred import portal
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500160 portal = portal.Portal(NetconfRealm(self, self.grpc_client))
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500161
162 # setup userid-password access
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500163 password_file = '{}/{}/{}'.format(dir_path,
164 C.CLIENT_CRED_DIRECTORY,
165 self.client_passwords_file)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500166 portal.registerChecker(FilePasswordDB(password_file))
167
168 # setup access when client uses keys
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500169 keys_file = '{}/{}/{}'.format(dir_path,
170 C.CLIENT_CRED_DIRECTORY,
171 self.client_public_keys_file)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500172 with open(keys_file) as f:
173 users = [line.rstrip('\n') for line in f]
174 users_dict = {}
175 for user in users:
176 users_dict[user.split(':')[0]] = [
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500177 keys.Key.fromFile('{}/{}/{}'.format(dir_path,
178 C.CLIENT_CRED_DIRECTORY,
179 user.split(':')[1]))]
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500180 sshDB = SSHPublicKeyChecker(InMemorySSHKeyDB(users_dict))
181 portal.registerChecker(sshDB)
182 return portal
183 except Exception as e:
184 log.error('setup-secure-access-fail', exception=repr(e))
185
186 @inlineCallbacks
187 def start_ssh_server(self):
188 try:
189 log.debug('starting', port=self.netconf_port)
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500190 self.set_capabilities()
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500191 self.portal = self.setup_secure_access()
192 self.connector = reactor.listenTCP(self.netconf_port, self)
193 log.debug('started', port=self.netconf_port)
194 self.d_stopped = Deferred()
195 self.d_stopped.callback(self.stop)
196 yield self.d_stopped
197 except Exception as e:
198 log.error('netconf-server-not-started', port=self.netconf_port,
199 exception=repr(e))
200
201 # Methods from SSHFactory
202 #
203
204 def protocol(self):
205 return SSHServerTransport()
206
207 def getPublicKeys(self):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500208 key_file_name = '{}/{}/{}'.format(dir_path,
209 C.KEYS_DIRECTORY,
210 self.server_public_key_file)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500211 try:
212 publicKeys = {
213 'ssh-rsa': keys.Key.fromFile(key_file_name)
214 }
215 return publicKeys
216 except Exception as e:
217 log.error('cannot-retrieve-server-public-key',
218 filename=key_file_name, exception=repr(e))
219
220 def getPrivateKeys(self):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500221 key_file_name = '{}/{}/{}'.format(dir_path,
222 C.KEYS_DIRECTORY,
223 self.server_private_key_file)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500224 try:
225 privateKeys = {
226 'ssh-rsa': keys.Key.fromFile(key_file_name)
227 }
228 return privateKeys
229 except Exception as e:
230 log.error('cannot-retrieve-server-private-key',
231 filename=key_file_name, exception=repr(e))