Search code examples
python-3.xazureazure-functionsftplibftps

Secure FTP deployment for Azure App using python (using TLS or FTPS)


I am trying to copy some files to an Azure App using python as part of a wider python script.

I can do this using FTP, but I can not figure out how to do it in a secure way using TLS.

I am using the credentials I downloaded from the Azure Portal clicking on "Get Publish Profile", then using them inside the python code like this:

import ftplib

ftpServer = ftplib.FTP_TLS(host='waws-<...>.ftp.azurewebsites.windows.net',
                        user='<MY_USER>\$<MY_USER>',
                        passwd='<VERY_LONG_PSW>')

however when executing ftpServer.cwd('<THE_PATH_TO_INSPECT>') then I see: ConnectionRefusedError: [Errno 111] Connection refused

When using ftplib.FTP(...) instead of ftplib.FTP_TLS(...) everything works as expected.

Looking at the Python docs here https://docs.python.org/3.5/library/ftplib.html it seems I should make sure there is some sort of implicit/explicit condition met, but I can not figure out what to do.

Reading on various web resources it seems I should use a different port than the standard 21, someone is saying 990, some other is saying 989, but I can not figure out where to specify this different port in the python code.

  • What's wrong with my code? Why I see Connection refused?
  • How can I make the python code establishing a secure TLS connection via FTP?

EDIT 1:

It seems this implicit connection for FTP is related to TLS version 1.2 (as per python docs), I am not 100% sure as I am not familiar with these standards, however I've tried anyway but without success. Bare in mind that <MY_USER>\$<MY_USER> (and password) comes from the content of the file I download from the Azure Portal as a "Publish Profile", see below:

$ python
Python 3.6.6 |Anaconda, Inc.| (default, Jun 28 2018, 17:14:51) 
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> 
>>> 
>>> 
>>> import ftplib
>>> 
>>> 
>>> ftpServer = ftplib.FTP_TLS(host='waws-<...>.ftp.azurewebsites.windows.net',
...                         user='<MY_USER>\$<MY_USER>',
...                         passwd='<VERY_LONG_PSW>')
>>> 
>>> 
>>> 
>>> ftpServer.dir()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/<MY_USER>/Conda/lib/python3.6/ftplib.py", line 575, in dir
    self.retrlines(cmd, func)
  File "/home/<MY_USER>/Conda/lib/python3.6/ftplib.py", line 468, in retrlines
    with self.transfercmd(cmd) as conn, \
  File "/home/<MY_USER>/Conda/lib/python3.6/ftplib.py", line 399, in transfercmd
    return self.ntransfercmd(cmd, rest)[0]
  File "/home/<MY_USER>/Conda/lib/python3.6/ftplib.py", line 798, in ntransfercmd
    conn, size = FTP.ntransfercmd(self, cmd, rest)
  File "/home/<MY_USER>/Conda/lib/python3.6/ftplib.py", line 361, in ntransfercmd
    source_address=self.source_address)
  File "/home/<MY_USER>/Conda/lib/python3.6/socket.py", line 724, in create_connection
    raise err
  File "/home/<MY_USER>/Conda/lib/python3.6/socket.py", line 713, in create_connection
    sock.connect(sa)
ConnectionRefusedError: [Errno 111] Connection refused
>>> 
>>> 

Solution

  • Seems to work just fine on implicit FTPS (990/TCP):

    $ openssl s_client -connect waws-prod-am2-119.ftp.azurewebsites.windows.net:990
    
    CONNECTED(00000003)
    [...]
    ---
    Certificate chain
     0 s:/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/
          CN=waws-prod-am2-119.publish.azurewebsites.windows.net
       i:/C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
     1 s:/C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
       i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
    ---
    [...]
    SSL handshake has read 3574 bytes and written 390 bytes
    Verification: OK
    ---
    New, TLSv1.2, Cipher is ECDHE-RSA-AES256-SHA384
    Server public key is 2048 bit
    [...]
    SSL-Session:
        Protocol  : TLSv1.2
        Cipher    : ECDHE-RSA-AES256-SHA384
        [...]
        Verify return code: 0 (ok)
        Extended master secret: yes
    ---
    220 Microsoft FTP Service
    

    The Python client library may be out of date or does not have a recent CA bundle containing the DigiCert CAs. You shouldn't be FTP-ing, try to upload to blob storage instead and have the Web App serve the files from that storage account (with a SAS signature if the files shouldn't be public).

    If you're trying to deploy the Web App, use a git repo or Web Deploy, it's much more reliable.

    Using ftplib in stock Python 3.6:

    >>> ftpServer = FTP_TLS(host='waws-prod-am2-119.ftp.azurewebsites.windows.net',
    ...                         user='myuser\$myuser',
    ...                         passwd='rM....dsFsfp')
    
    >>> ftpServer.dir()
    03-21-17  09:27AM       <DIR>          .ssh
    02-22-18  08:19AM       <DIR>          ciphers
    02-22-18  10:28AM       <DIR>          data
    01-12-18  12:58AM       <DIR>          LogFiles
    02-22-18  08:16AM       <DIR>          site
    
    >>> ftpServer.cwd('data')
    '250 CWD command successful.'