Allow selection of SSL certificates and pgpass files in connection properties. Fixes #2649. Fixes #2650

This commit is contained in:
Murtuza Zabuawala
2017-09-28 10:02:33 +01:00
committed by Dave Page
parent 510bc6c974
commit f855ed88ce
14 changed files with 439 additions and 58 deletions

View File

@@ -25,7 +25,7 @@ import config
from config import PG_DEFAULT_DRIVER
from pgadmin.model import db, Server, ServerGroup, User
from pgadmin.utils.driver import get_driver
from pgadmin.utils import get_storage_directory
def has_any(data, keys):
"""
@@ -230,7 +230,51 @@ class ServerNode(PGChildNodeView):
'((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$'
pat4 = re.compile(EXP_IP4)
pat6 = re.compile(EXP_IP6)
SSL_MODES = ['prefer','require', 'verify-ca', 'verify-full']
def check_ssl_fields(self, data):
"""
This function will allow us to check and set defaults for
SSL fields
Args:
data: Response data
Returns:
Flag and Data
"""
flag = False
if 'sslmode' in data and data['sslmode'] in self.SSL_MODES:
flag = True
ssl_fields = [
'sslcert', 'sslkey', 'sslrootcert', 'sslcrl', 'sslcompression'
]
# Required SSL fields for SERVER mode from user
required_ssl_fields_server_mode = ['sslcert', 'sslkey']
for field in ssl_fields:
if field not in data:
# In Server mode,
# we will set dummy SSL certificate file path which will
# prevent using default SSL certificates from web servers
if config.SERVER_MODE and \
field in required_ssl_fields_server_mode:
# Set file manager directory from preference
import os
storage_dir = get_storage_directory()
file_extn = '.key' if field.endswith('key') else '.crt'
dummy_ssl_file = os.path.join(
storage_dir, '.postgresql',
'postgresql' + file_extn
)
data[field] = dummy_ssl_file
# For Desktop mode, we will allow to default
else:
data[field] = None
return flag, data
def nodes(self, gid):
res = []
@@ -381,7 +425,13 @@ class ServerNode(PGChildNodeView):
'gid': 'servergroup_id',
'comment': 'comment',
'role': 'role',
'db_res': 'db_res'
'db_res': 'db_res',
'passfile': 'passfile',
'sslcert': 'sslcert',
'sslkey': 'sslkey',
'sslrootcert': 'sslrootcert',
'sslcrl': 'sslcrl',
'sslcompression': 'sslcompression'
}
disp_lbl = {
@@ -411,8 +461,6 @@ class ServerNode(PGChildNodeView):
errormsg=gettext('Host address not valid')
)
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
connected = conn.connected()
@@ -430,7 +478,12 @@ class ServerNode(PGChildNodeView):
for arg in config_param_map:
if arg in data:
setattr(server, config_param_map[arg], data[arg])
value = data[arg]
# sqlite3 do not have boolean type so we need to convert
# it manually to integer
if arg == 'sslcompression':
value = 1 if value else 0
setattr(server, config_param_map[arg], value)
idx += 1
if idx == 0:
@@ -532,6 +585,8 @@ class ServerNode(PGChildNodeView):
conn = manager.connection()
connected = conn.connected()
is_ssl = True if server.ssl_mode in self.SSL_MODES else False
return ajax_response(
response={
'id': server.id,
@@ -549,7 +604,14 @@ class ServerNode(PGChildNodeView):
'version': manager.ver,
'sslmode': server.ssl_mode,
'server_type': manager.server_type if connected else 'pg',
'db_res': server.db_res.split(',') if server.db_res else None
'db_res': server.db_res.split(',') if server.db_res else None,
'passfile': server.passfile if server.passfile else None,
'sslcert': server.sslcert if is_ssl else None,
'sslkey': server.sslkey if is_ssl else None,
'sslrootcert': server.sslrootcert if is_ssl else None,
'sslcrl': server.sslcrl if is_ssl else None,
'sslcompression': True if is_ssl and server.sslcompression
else False
}
)
@@ -588,6 +650,9 @@ class ServerNode(PGChildNodeView):
errormsg=gettext('Host address not valid')
)
# To check ssl configuration
is_ssl, data = self.check_ssl_fields(data)
server = None
try:
@@ -603,7 +668,12 @@ class ServerNode(PGChildNodeView):
ssl_mode=data[u'sslmode'],
comment=data[u'comment'] if u'comment' in data else None,
role=data[u'role'] if u'role' in data else None,
db_res=','.join(data[u'db_res']) if u'db_res' in data else None
db_res=','.join(data[u'db_res']) if u'db_res' in data else None,
sslcert=data['sslcert'] if is_ssl else None,
sslkey=data['sslkey'] if is_ssl else None,
sslrootcert=data['sslrootcert'] if is_ssl else None,
sslcrl=data['sslcrl'] if is_ssl else None,
sslcompression=1 if is_ssl and data['sslcompression'] else 0
)
db.session.add(server)
db.session.commit()
@@ -621,14 +691,21 @@ class ServerNode(PGChildNodeView):
if 'password' in data and data["password"] != '':
# login with password
have_password = True
passfile = None
password = data['password']
password = encrypt(password, current_user.password)
elif 'passfile' in data and data["passfile"] != '':
passfile = data['passfile']
setattr(server, 'passfile', passfile)
db.session.commit()
else:
# Attempt password less login
password = None
passfile = None
status, errmsg = conn.connect(
password=password,
passfile=passfile,
server_types=ServerType.types()
)
if hasattr(str, 'decode') and errmsg is not None:
@@ -774,6 +851,7 @@ class ServerNode(PGChildNodeView):
) if request.data else {}
password = None
passfile = None
save_password = False
# Connect the Server
@@ -782,7 +860,8 @@ class ServerNode(PGChildNodeView):
if 'password' not in data:
conn_passwd = getattr(conn, 'password', None)
if conn_passwd is None and server.password is None:
if conn_passwd is None and server.password is None and \
server.passfile is None:
# Return the password template in case password is not
# provided, or password has not been saved earlier.
return make_json_response(
@@ -795,6 +874,8 @@ class ServerNode(PGChildNodeView):
_=gettext
)
)
elif server.passfile and server.passfile != '':
passfile = server.passfile
else:
password = conn_passwd or server.password
else:
@@ -815,6 +896,7 @@ class ServerNode(PGChildNodeView):
try:
status, errmsg = conn.connect(
password=password,
passfile=passfile,
server_types=ServerType.types()
)
except Exception as e:

View File

@@ -10,6 +10,7 @@ define('pgadmin.node.server', [
) {
if (!pgBrowser.Nodes['server']) {
var SSL_MODES = ['prefer', 'require', 'verify-ca', 'verify-full'];
var SecurityModel = pgBrowser.SecLabelModel = pgBrowser.Node.Model.extend({
defaults: {
@@ -611,7 +612,13 @@ define('pgadmin.node.server', [
connect_now: true,
password: undefined,
save_password: false,
db_res: ''
db_res: '',
passfile: undefined,
sslcompression: false,
sslcert: undefined,
sslkey: undefined,
sslrootcert: undefined,
sslcrl: undefined
},
// Default values!
initialize: function(attrs, args) {
@@ -653,13 +660,6 @@ define('pgadmin.node.server', [
},{
id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
},{
id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
},{
id: 'db_res', label: gettext('DB restriction'), type: 'select2', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected', select2: {multiple: true, allowClear: false,
tags: true, tokenSeparators: [','], first_empty: false, selectOnClose: true, emptyOptions: true}
},{
id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected', min: 1024, max: 65535
@@ -688,7 +688,7 @@ define('pgadmin.node.server', [
id: 'role', label: gettext('Role'), type: 'text', group: gettext('Connection'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
},{
id: 'sslmode', label: gettext('SSL mode'), type: 'options', group: gettext('Connection'),
id: 'sslmode', label: gettext('SSL mode'), type: 'options', group: gettext('SSL'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
'options': [
{label: 'Allow', value: 'allow'},
@@ -698,6 +698,96 @@ define('pgadmin.node.server', [
{label: 'Verify-CA', value: 'verify-ca'},
{label: 'Verify-Full', value: 'verify-full'}
]
},{
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: 'isSSL', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*'],
deps: ['sslmode']
},{
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: 'isSSL', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*'],
deps: ['sslmode']
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: 'isSSL', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*'],
deps: ['sslmode']
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
group: gettext('SSL'), mode: ['edit', 'create'],
disabled: 'isSSL', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*'],
deps: ['sslmode']
},{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['edit', 'create'], group: gettext('SSL'),
'options': { 'onText': 'True', 'offText': 'False',
'onColor': 'success', 'offColor': 'danger', 'size': 'small'},
deps: ['sslmode'], disabled: 'isSSL'
},{
id: 'sslcert', label: gettext('Client certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(m) {
var sslcert = m.get('sslcert');
return !_.isUndefined(sslcert) && !_.isNull(sslcert);
}
},{
id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(m) {
var sslkey = m.get('sslkey');
return !_.isUndefined(sslkey) && !_.isNull(sslkey);
}
},{
id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(m) {
var sslrootcert = m.get('sslrootcert');
return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
}
},{
id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
group: gettext('SSL'), mode: ['properties'],
deps: ['sslmode'],
visible: function(m) {
var sslcrl = m.get('sslcrl');
return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
}
},{
id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
mode: ['properties'], group: gettext('SSL'),
'options': { 'onText': 'True', 'offText': 'False',
'onColor': 'success', 'offColor': 'danger', 'size': 'small'},
deps: ['sslmode'], visible: function(m) {
var sslmode = m.get('sslmode');
return _.indexOf(SSL_MODES, sslmode) != -1;
}
},{
id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected'
},{
id: 'db_res', label: gettext('DB restriction'), type: 'select2', group: gettext('Advanced'),
mode: ['properties', 'edit', 'create'], disabled: 'isConnected', select2: {multiple: true, allowClear: false,
tags: true, tokenSeparators: [','], first_empty: false, selectOnClose: true, emptyOptions: true}
},{
id: 'passfile', label: gettext('Password File'), type: 'text',
group: gettext('Advanced'), mode: ['edit', 'create'],
disabled: 'isConnected', control: Backform.FileControl,
dialog_type: 'select_file', supp_types: ['*']
},{
id: 'passfile', label: gettext('Password File'), type: 'text',
group: gettext('Advanced'), mode: ['properties'],
visible: function(m) {
var passfile = m.get('passfile');
return !_.isUndefined(passfile) && !_.isNull(passfile);
}
}],
validate: function() {
var err = {},
@@ -790,6 +880,14 @@ define('pgadmin.node.server', [
},
isConnected: function(model) {
return model.get('connected');
},
isSSL: function(model) {
var ssl_mode = model.get('sslmode');
// If server is not connected and have required SSL option
if(model.get('connected')) {
return true;
}
return _.indexOf(SSL_MODES, ssl_mode) == -1;
}
}),
connection_lost: function(i, resp) {