mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Allow selection of SSL certificates and pgpass files in connection properties. Fixes #2649. Fixes #2650
This commit is contained in:
committed by
Dave Page
parent
510bc6c974
commit
f855ed88ce
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user