blob: 3b22290824e0da3ec84cc4eff1f0e1bb38070929 [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
19from twisted.conch import avatar
20from twisted.cred import portal
21from twisted.conch.checkers import SSHPublicKeyChecker, InMemorySSHKeyDB
22from twisted.conch.ssh import factory, userauth, connection, keys, session
23from twisted.conch.ssh.transport import SSHServerTransport
24
25from twisted.cred.checkers import FilePasswordDB
26from twisted.internet import reactor
27from twisted.internet.defer import Deferred, inlineCallbacks
28# from twisted.python import log as logp
29from zope.interface import implementer
Khen Nursimulua7b842a2016-12-03 23:28:42 -050030from session.nc_protocol_handler import NetconfProtocolHandler
31from session.nc_connection import NetconfConnection
32from session.session_mgr import get_session_manager_instance
33from constants import Constants as C
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050034
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050035
36# logp.startLogging(sys.stderr)
37
38log = structlog.get_logger()
39
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050040
41# @implementer(conchinterfaces.ISession)
42class NetconfAvatar(avatar.ConchUser):
43 def __init__(self, username, nc_server, grpc_stub):
44 avatar.ConchUser.__init__(self)
45 self.username = username
46 self.nc_server = nc_server
47 self.grpc_stub = grpc_stub
48 self.channelLookup.update({'session': session.SSHSession})
49 self.subsystemLookup.update(
50 {b"netconf": NetconfConnection})
51
52 def get_grpc_stub(self):
53 return self.grpc_stub
54
55 def get_nc_server(self):
56 return self.nc_server
57
Khen Nursimulua7b842a2016-12-03 23:28:42 -050058 def get_user(self):
59 return self.username
60
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050061 def logout(self):
62 log.info('netconf-avatar-logout', username=self.username)
63
64
65@implementer(portal.IRealm)
66class NetconfRealm(object):
67 def __init__(self, nc_server, grpc_stub):
68 self.grpc_stub = grpc_stub
69 self.nc_server = nc_server
70
71 def requestAvatar(self, avatarId, mind, *interfaces):
72 user = NetconfAvatar(avatarId, self.nc_server, self.grpc_stub)
73 return interfaces[0], user, user.logout
74
75
76class NCServer(factory.SSHFactory):
77 #
78 services = {
79 'ssh-userauth': userauth.SSHUserAuthServer,
80 'ssh-connection': connection.SSHConnection
81 }
82
83 def __init__(self,
84 netconf_port,
85 server_private_key_file,
86 server_public_key_file,
87 client_public_keys_file,
88 client_passwords_file,
89 grpc_stub):
90
91 self.netconf_port = netconf_port
92 self.server_private_key_file = server_private_key_file
93 self.server_public_key_file = server_public_key_file
94 self.client_public_keys_file = client_public_keys_file
95 self.client_passwords_file = client_passwords_file
Khen Nursimulua7b842a2016-12-03 23:28:42 -050096 self.session_mgr = get_session_manager_instance()
Khen Nursimulu3869d8d2016-11-28 20:44:28 -050097 self.grpc_stub = grpc_stub
98 self.connector = None
99 self.nc_client_map = {}
100 self.running = False
101 self.exiting = False
102
103 def start(self):
104 log.debug('starting')
105 if self.running:
106 return
107 self.running = True
108 reactor.callLater(0, self.start_ssh_server)
109 log.info('started')
110 return self
111
112 def stop(self):
113 log.debug('stopping')
114 self.exiting = True
115 self.connector.disconnect()
116 self.d_stopped.callback(None)
117 log.info('stopped')
118
119 def client_disconnected(self, result, handler, reason):
120 assert isinstance(handler, NetconfProtocolHandler)
121
122 log.info('client-disconnected', reason=reason)
123
124 # For now just nullify the handler
125 handler.close()
126
127 def client_connected(self, client_conn):
128 assert isinstance(client_conn, NetconfConnection)
129 log.info('client-connected')
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500130
131 #create a session
132 session = self.session_mgr.create_session(client_conn.avatar.get_user())
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500133 handler = NetconfProtocolHandler(self, client_conn,
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500134 session, self.grpc_stub)
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500135 client_conn.proto_handler = handler
136 reactor.callLater(0, handler.start)
137
138 def setup_secure_access(self):
139 try:
140 from twisted.cred import portal
141 portal = portal.Portal(NetconfRealm(self, self.grpc_stub))
142
143 # setup userid-password access
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500144 password_file = '{}/{}'.format(C.CLIENT_CRED_DIRECTORY,
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500145 self.client_passwords_file)
146 portal.registerChecker(FilePasswordDB(password_file))
147
148 # setup access when client uses keys
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500149 keys_file = '{}/{}'.format(C.CLIENT_CRED_DIRECTORY,
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500150 self.client_public_keys_file)
151 with open(keys_file) as f:
152 users = [line.rstrip('\n') for line in f]
153 users_dict = {}
154 for user in users:
155 users_dict[user.split(':')[0]] = [
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500156 keys.Key.fromFile('{}/{}'.format(C.CLIENT_CRED_DIRECTORY,
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500157 user.split(':')[1]))]
158 sshDB = SSHPublicKeyChecker(InMemorySSHKeyDB(users_dict))
159 portal.registerChecker(sshDB)
160 return portal
161 except Exception as e:
162 log.error('setup-secure-access-fail', exception=repr(e))
163
164 @inlineCallbacks
165 def start_ssh_server(self):
166 try:
167 log.debug('starting', port=self.netconf_port)
168 self.portal = self.setup_secure_access()
169 self.connector = reactor.listenTCP(self.netconf_port, self)
170 log.debug('started', port=self.netconf_port)
171 self.d_stopped = Deferred()
172 self.d_stopped.callback(self.stop)
173 yield self.d_stopped
174 except Exception as e:
175 log.error('netconf-server-not-started', port=self.netconf_port,
176 exception=repr(e))
177
178 # Methods from SSHFactory
179 #
180
181 def protocol(self):
182 return SSHServerTransport()
183
184 def getPublicKeys(self):
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500185 key_file_name = '{}/{}'.format(C.KEYS_DIRECTORY,
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500186 self.server_public_key_file)
187 try:
188 publicKeys = {
189 'ssh-rsa': keys.Key.fromFile(key_file_name)
190 }
191 return publicKeys
192 except Exception as e:
193 log.error('cannot-retrieve-server-public-key',
194 filename=key_file_name, exception=repr(e))
195
196 def getPrivateKeys(self):
Khen Nursimulua7b842a2016-12-03 23:28:42 -0500197 key_file_name = '{}/{}'.format(C.KEYS_DIRECTORY,
Khen Nursimulu3869d8d2016-11-28 20:44:28 -0500198 self.server_private_key_file)
199 try:
200 privateKeys = {
201 'ssh-rsa': keys.Key.fromFile(key_file_name)
202 }
203 return privateKeys
204 except Exception as e:
205 log.error('cannot-retrieve-server-private-key',
206 filename=key_file_name, exception=repr(e))