blob: 6c43194df92c897599b1c16520e3a71475fe25f1 [file] [log] [blame]
Khen Nursimulu3869d8d2016-11-28 20:44:28 -05001#
2# Copyright 2016 the original author or authors.
3#
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 Nursimulu3869d8d2016-11-28 20:44:28 -050035
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050036dir_path = os.path.dirname(os.path.realpath(__file__))
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050037
38# logp.startLogging(sys.stderr)
39
40log = structlog.get_logger()
41
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050042
43# @implementer(conchinterfaces.ISession)
44class NetconfAvatar(avatar.ConchUser):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050045 def __init__(self, username, nc_server, grpc_client):
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050046 avatar.ConchUser.__init__(self)
47 self.username = username
48 self.nc_server = nc_server
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050049 self.grpc_client = grpc_client
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050050 self.channelLookup.update({'session': session.SSHSession})
51 self.subsystemLookup.update(
52 {b"netconf": NetconfConnection})
53
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050054 def get_grpc_client(self):
55 return self.grpc_client
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050056
57 def get_nc_server(self):
58 return self.nc_server
59
Khen Nursimulua7b842a2016-12-03 23:28:42 -050060 def get_user(self):
61 return self.username
62
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050063 def logout(self):
64 log.info('netconf-avatar-logout', username=self.username)
65
66
67@implementer(portal.IRealm)
68class NetconfRealm(object):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050069 def __init__(self, nc_server, grpc_client):
70 self.grpc_client = grpc_client
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050071 self.nc_server = nc_server
72
73 def requestAvatar(self, avatarId, mind, *interfaces):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050074 user = NetconfAvatar(avatarId, self.nc_server, self.grpc_client)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050075 return interfaces[0], user, user.logout
76
77
78class NCServer(factory.SSHFactory):
79 #
80 services = {
81 'ssh-userauth': userauth.SSHUserAuthServer,
82 'ssh-connection': connection.SSHConnection
83 }
84
85 def __init__(self,
86 netconf_port,
87 server_private_key_file,
88 server_public_key_file,
89 client_public_keys_file,
90 client_passwords_file,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050091 grpc_client):
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050092
93 self.netconf_port = netconf_port
94 self.server_private_key_file = server_private_key_file
95 self.server_public_key_file = server_public_key_file
96 self.client_public_keys_file = client_public_keys_file
97 self.client_passwords_file = client_passwords_file
Khen Nursimulua7b842a2016-12-03 23:28:42 -050098 self.session_mgr = get_session_manager_instance()
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050099 self.grpc_client = grpc_client
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500100 self.connector = None
101 self.nc_client_map = {}
102 self.running = False
103 self.exiting = False
104
105 def start(self):
106 log.debug('starting')
107 if self.running:
108 return
109 self.running = True
110 reactor.callLater(0, self.start_ssh_server)
111 log.info('started')
112 return self
113
114 def stop(self):
115 log.debug('stopping')
116 self.exiting = True
117 self.connector.disconnect()
118 self.d_stopped.callback(None)
119 log.info('stopped')
120
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500121 def reload_capabilities(self):
122 # TODO: Called when there is a reconnect to voltha
123 # If there are new device types then the new
124 # capabilities will be exposed for subsequent client connections to use
125 pass
126
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500127 def client_disconnected(self, result, handler, reason):
128 assert isinstance(handler, NetconfProtocolHandler)
129
130 log.info('client-disconnected', reason=reason)
131
132 # For now just nullify the handler
133 handler.close()
134
135 def client_connected(self, client_conn):
136 assert isinstance(client_conn, NetconfConnection)
137 log.info('client-connected')
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500138
139 #create a session
140 session = self.session_mgr.create_session(client_conn.avatar.get_user())
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500141 handler = NetconfProtocolHandler(self, client_conn,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500142 session, self.grpc_client)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500143 client_conn.proto_handler = handler
144 reactor.callLater(0, handler.start)
145
146 def setup_secure_access(self):
147 try:
148 from twisted.cred import portal
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500149 portal = portal.Portal(NetconfRealm(self, self.grpc_client))
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500150
151 # setup userid-password access
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500152 password_file = '{}/{}/{}'.format(dir_path,
153 C.CLIENT_CRED_DIRECTORY,
154 self.client_passwords_file)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500155 portal.registerChecker(FilePasswordDB(password_file))
156
157 # setup access when client uses keys
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500158 keys_file = '{}/{}/{}'.format(dir_path,
159 C.CLIENT_CRED_DIRECTORY,
160 self.client_public_keys_file)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500161 with open(keys_file) as f:
162 users = [line.rstrip('\n') for line in f]
163 users_dict = {}
164 for user in users:
165 users_dict[user.split(':')[0]] = [
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500166 keys.Key.fromFile('{}/{}/{}'.format(dir_path,
167 C.CLIENT_CRED_DIRECTORY,
168 user.split(':')[1]))]
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500169 sshDB = SSHPublicKeyChecker(InMemorySSHKeyDB(users_dict))
170 portal.registerChecker(sshDB)
171 return portal
172 except Exception as e:
173 log.error('setup-secure-access-fail', exception=repr(e))
174
175 @inlineCallbacks
176 def start_ssh_server(self):
177 try:
178 log.debug('starting', port=self.netconf_port)
179 self.portal = self.setup_secure_access()
180 self.connector = reactor.listenTCP(self.netconf_port, self)
181 log.debug('started', port=self.netconf_port)
182 self.d_stopped = Deferred()
183 self.d_stopped.callback(self.stop)
184 yield self.d_stopped
185 except Exception as e:
186 log.error('netconf-server-not-started', port=self.netconf_port,
187 exception=repr(e))
188
189 # Methods from SSHFactory
190 #
191
192 def protocol(self):
193 return SSHServerTransport()
194
195 def getPublicKeys(self):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500196 key_file_name = '{}/{}/{}'.format(dir_path,
197 C.KEYS_DIRECTORY,
198 self.server_public_key_file)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500199 try:
200 publicKeys = {
201 'ssh-rsa': keys.Key.fromFile(key_file_name)
202 }
203 return publicKeys
204 except Exception as e:
205 log.error('cannot-retrieve-server-public-key',
206 filename=key_file_name, exception=repr(e))
207
208 def getPrivateKeys(self):
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500209 key_file_name = '{}/{}/{}'.format(dir_path,
210 C.KEYS_DIRECTORY,
211 self.server_private_key_file)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500212 try:
213 privateKeys = {
214 'ssh-rsa': keys.Key.fromFile(key_file_name)
215 }
216 return privateKeys
217 except Exception as e:
218 log.error('cannot-retrieve-server-private-key',
219 filename=key_file_name, exception=repr(e))