SFTP destination plugin (#1170)

* add sftp destination plugin
This commit is contained in:
Dmitry Zykov 2018-04-03 20:30:19 +03:00 committed by kevgliss
parent fb494bc32a
commit 4a0103a88d
6 changed files with 190 additions and 5 deletions

View File

@ -30,7 +30,7 @@ pem==17.1.0
raven[flask]==6.1.0 raven[flask]==6.1.0
jinja2==2.9.6 jinja2==2.9.6
# pyldap==2.4.37 # cannot be installed on rtd - required by ldap auth provider # pyldap==2.4.37 # cannot be installed on rtd - required by ldap auth provider
paramiko==2.2.1 # required for lemur_linuxdst plugin paramiko==2.4.1 # required for the SFTP destination plugin
sphinx sphinx
sphinxcontrib-httpdomain sphinxcontrib-httpdomain
sphinx-rtd-theme sphinx-rtd-theme

View File

@ -0,0 +1,5 @@
try:
VERSION = __import__('pkg_resources') \
.get_distribution(__name__).version
except Exception as e:
VERSION = 'unknown'

View File

@ -0,0 +1,179 @@
"""
.. module: lemur.plugins.lemur_sftp.plugin
:platform: Unix
:synopsis: Allow the uploading of certificates to SFTP.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
Allow the uploading of certificates to SFTP.
NGINX and Apache export formats are supported.
Password and RSA private key are supported.
Passwords are not encrypted and stored as a plain text.
Detailed logging when Lemur debug mode is enabled.
.. moduleauthor:: Dmitry Zykov https://github.com/DmitryZykov
"""
import paramiko
from flask import current_app
from lemur.plugins import lemur_sftp
from lemur.common.defaults import common_name
from lemur.common.utils import parse_certificate
from lemur.plugins.bases import DestinationPlugin
class SFTPDestinationPlugin(DestinationPlugin):
title = 'SFTP'
slug = 'sftp-destination'
description = 'Allow the uploading of certificates to SFTP'
version = lemur_sftp.VERSION
author = 'Dmitry Zykov'
author_url = 'https://github.com/DmitryZykov'
options = [
{
'name': 'host',
'type': 'str',
'required': True,
'helpMessage': 'The SFTP host.'
},
{
'name': 'port',
'type': 'int',
'required': True,
'helpMessage': 'The SFTP port, default is 22.',
'validation': '^(6553[0-5]|655[0-2][0-9]\d|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|[1-9](\d){0,3})',
'default': '22'
},
{
'name': 'user',
'type': 'str',
'required': True,
'helpMessage': 'The SFTP user. Default is root.',
'default': 'root'
},
{
'name': 'password',
'type': 'str',
'required': False,
'helpMessage': 'The SFTP password (optional when the private key is used).',
'default': None
},
{
'name': 'privateKeyPath',
'type': 'str',
'required': False,
'helpMessage': 'The path to the RSA private key on the Lemur server (optional).',
'default': None
},
{
'name': 'privateKeyPass',
'type': 'str',
'required': False,
'helpMessage': 'The password for the encrypted RSA private key (optional).',
'default': None
},
{
'name': 'destinationPath',
'type': 'str',
'required': True,
'helpMessage': 'The SFTP path where certificates will be uploaded.',
'default': '/etc/nginx/certs'
},
{
'name': 'exportFormat',
'required': True,
'value': 'NGINX',
'helpMessage': 'The export format for certificates.',
'type': 'select',
'available': [
'NGINX',
'Apache'
]
}
]
def upload(self, name, body, private_key, cert_chain, options, **kwargs):
current_app.logger.debug('SFTP destination plugin is started')
cn = common_name(parse_certificate(body))
host = self.get_option('host', options)
port = self.get_option('port', options)
user = self.get_option('user', options)
password = self.get_option('password', options)
ssh_priv_key = self.get_option('privateKeyPath', options)
ssh_priv_key_pass = self.get_option('privateKeyPass', options)
dst_path = self.get_option('destinationPath', options)
export_format = self.get_option('exportFormat', options)
# prepare files for upload
files = {cn + '.key': private_key,
cn + '.pem': body}
if cert_chain:
if export_format == 'NGINX':
# assemble body + chain in the single file
files[cn + '.pem'] += '\n' + cert_chain
elif export_format == 'Apache':
# store chain in the separate file
files[cn + '.ca.bundle.pem'] = cert_chain
# upload files
try:
current_app.logger.debug('Connecting to {0}@{1}:{2}'.format(user, host, port))
ssh = paramiko.SSHClient()
# allow connection to the new unknown host
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# open the ssh connection
if password:
current_app.logger.debug('Using password')
ssh.connect(host, username=user, port=port, password=password)
elif ssh_priv_key:
current_app.logger.debug('Using RSA private key')
pkey = paramiko.RSAKey.from_private_key_file(ssh_priv_key, ssh_priv_key_pass)
ssh.connect(host, username=user, port=port, pkey=pkey)
else:
current_app.logger.error("No password or private key provided. Can't proceed")
raise paramiko.ssh_exception.AuthenticationException
# open the sftp session inside the ssh connection
sftp = ssh.open_sftp()
# make sure that the destination path exist
try:
current_app.logger.debug('Creating {0}'.format(dst_path))
sftp.mkdir(dst_path)
except IOError:
current_app.logger.debug('{0} already exist, resuming'.format(dst_path))
try:
dst_path_cn = dst_path + '/' + cn
current_app.logger.debug('Creating {0}'.format(dst_path_cn))
sftp.mkdir(dst_path_cn)
except IOError:
current_app.logger.debug('{0} already exist, resuming'.format(dst_path_cn))
# upload certificate files to the sftp destination
for filename, data in files.items():
current_app.logger.debug('Uploading {0} to {1}'.format(filename, dst_path_cn))
with sftp.open(dst_path_cn + '/' + filename, 'w') as f:
f.write(data)
# read only for owner, -r--------
sftp.chmod(dst_path_cn + '/' + filename, 0o400)
ssh.close()
except Exception as e:
current_app.logger.error('ERROR in {0}: {1}'.format(e.__class__, e))
try:
ssh.close()
except BaseException:
pass

View File

@ -42,7 +42,7 @@
{{ item.name | titleCase }} {{ item.name | titleCase }}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="/^[0-9]{12,12}$/" <input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="item.validation?item.validation:'^[0-9]+$'"
class="form-control" ng-model="item.value"/> class="form-control" ng-model="item.value"/>
<select name="sub" ng-if="item.type == 'select'" class="form-control" ng-options="i for i in item.available" <select name="sub" ng-if="item.type == 'select'" class="form-control" ng-options="i for i in item.available"
ng-model="item.value"></select> ng-model="item.value"></select>
@ -61,7 +61,7 @@
{{ item.name | titleCase }} {{ item.name | titleCase }}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="/^[0-9]{12,12}$/" <input name="sub" ng-if="item.type == 'int'" type="number" ng-pattern="item.validation?item.validation:'^[0-9]+$'"
class="form-control" ng-model="item.value"/> class="form-control" ng-model="item.value"/>
<select name="sub" ng-if="item.type == 'select'" class="form-control" <select name="sub" ng-if="item.type == 'select'" class="form-control"
ng-options="i for i in item.available" ng-model="item.value"></select> ng-options="i for i in item.available" ng-model="item.value"></select>

View File

@ -19,7 +19,7 @@ lockfile
marshmallow-sqlalchemy marshmallow-sqlalchemy
marshmallow marshmallow
ndg-httpsclient ndg-httpsclient
paramiko # required for lemur_linuxdst plugin paramiko # required for the SFTP destination plugin
pem pem
psycopg2 psycopg2
pyjwt pyjwt

View File

@ -147,7 +147,8 @@ setup(
'digicert_issuer = lemur.plugins.lemur_digicert.plugin:DigiCertIssuerPlugin', 'digicert_issuer = lemur.plugins.lemur_digicert.plugin:DigiCertIssuerPlugin',
'digicert_cis_issuer = lemur.plugins.lemur_digicert.plugin:DigiCertCISIssuerPlugin', 'digicert_cis_issuer = lemur.plugins.lemur_digicert.plugin:DigiCertCISIssuerPlugin',
'digicert_cis_source = lemur.plugins.lemur_digicert.plugin:DigiCertCISSourcePlugin', 'digicert_cis_source = lemur.plugins.lemur_digicert.plugin:DigiCertCISSourcePlugin',
'csr_export = lemur.plugins.lemur_csr.plugin:CSRExportPlugin' 'csr_export = lemur.plugins.lemur_csr.plugin:CSRExportPlugin',
'sftp_destination = lemur.plugins.lemur_sftp.plugin:SFTPDestinationPlugin'
], ],
}, },
classifiers=[ classifiers=[