Repository: Miserlou/Zappa

Issue:
Unable to successfully zappa certify on windows
Attempting to certify with either a local key file or one on s3.

First issue is an error like: No such file or directory: u'/tmp/account.key'
Created c:\tmp and got past that.

Now get
Calling certify for environment dev..
Certifying domain <domain> ..
OpenSSL Error: The filename, directory name, or volume label syntax is incorrect.

Failed to generate or install certificate! :(


Discussion:
Oh man, yeah. This is a pretty big one.

Windows is kind of a second class citizen until we can find somebody to help maintain support for that platform.
Well, for what it's worth I:
0. Installed GetGnuWin32 to get cat and openssl
1. created c:\tmp
2. made sure openssl was in my path
3. set OPENSSL_CONF to my openssl.cnf
4. edited all Popen calls in letsencrypt.py to pass command as string, not list.

That got me through. Waiting to see if it actually worked.
:-D!
@seanpaley I got lost at step 3.

So I tried this:

- Download Git package for Windows here: https://git-scm.com/download/win
- Chose advanced options and installed Git Bash command line tools.
- Ran C:\Program Files\git-bash.exe
- Ran `openssl genrsa 2048 > account.key;`
- Activated Windows virtualenv where Zappa was installed. Use either `. Scripts/activate` or `source Scripts/activate` ([reference](http://stackoverflow.com/a/10451017/1783439))
- Ran `zappa certify`

I am still getting the original error: 

> OpenSSL Error: The filename, directory name, or volume label syntax is incorrect.


Any advice is welcome.
Hi @nueverest I actually switched back to using a mac, so I can't validate all the steps any more. Did you create a `c:\tmp` directory? The code expects that dir to exist and might be related to your issue.
@seanpaley I just confirmed that `c:\tmp` exists.
Sorry. Going from memory.  #3 means to set an env variable OPENSSL_CONF to wherever your openssl.cnf is.  #4 is the painful one - I guess there is a difference with Popen on windows. The calls pass the command as a [], but I had to edit the file to pass as a string. Not sure if there was a better way but it worked.
I was having the same issue. I followed all of @seanpaley suggestions, and that fixed it. I'm not sure which specifically fixed it, except I think that 4 was required. I did 1-3 with no luck. Then, I cloned the repo and changed the popen calls. That did it for me.

I am now getting "Domain challenge did not pass", but I'm assuming that it's user error on my end, or at least unrelated to this issue.

Edit: I figured out the remaining issues (just config stuff on my end), and everything is working fine now. 

Resolving patch:
diff --git a/zappa/cli.py b/zappa/cli.py
--- a/zappa/cli.py
+++ b/zappa/cli.py
@@ -1670,10 +1670,10 @@ def certify(self, no_cleanup=False, no_confirm=True, manual=False):
             # Get install account_key to /tmp/account_key.pem
             if account_key_location.startswith('s3://'):
                 bucket, key_name = parse_s3_url(account_key_location)
-                self.zappa.s3_client.download_file(bucket, key_name, '/tmp/account.key')
+                self.zappa.s3_client.download_file(bucket, key_name, '{}/account.key'.format(tempfile.gettempdir()))
             else:
                 from shutil import copyfile
-                copyfile(account_key_location, '/tmp/account.key')
+                copyfile(account_key_location, '{}/account.key'.format(tempfile.gettempdir()))
 
         # Prepare for Custom SSL
         elif not account_key_location and not cert_arn:
diff --git a/zappa/letsencrypt.py b/zappa/letsencrypt.py
--- a/zappa/letsencrypt.py
+++ b/zappa/letsencrypt.py
@@ -18,7 +18,9 @@
 import re
 import subprocess
 import os
+import sys
 import time
+import tempfile
 
 import binascii
 import textwrap
@@ -40,6 +42,17 @@
 LOGGER.addHandler(logging.StreamHandler())
 
 
+def Popen(command, *args, **kwargs):
+    if not kwargs.get('shell'):
+        return subprocess.Popen(command, *args, **kwargs)
+
+    mswindows = (sys.platform == "win32")
+    if mswindows:
+        return subprocess.Popen(command[0], *args, **kwargs)
+    else:
+        return subprocess.Popen(command, *args, **kwargs)
+
+
 def get_cert_and_update_domain(
                                 zappa_instance,
                                 lambda_name,
@@ -58,13 +71,13 @@ def get_cert_and_update_domain(
         get_cert(zappa_instance)
         create_chained_certificate()
 
-        with open('/tmp/signed.crt') as f:
+        with open('{}/signed.crt'.format(tempfile.gettempdir())) as f:
             certificate_body = f.read()
 
-        with open('/tmp/domain.key') as f:
+        with open('{}/domain.key'.format(tempfile.gettempdir())) as f:
             certificate_private_key = f.read()
 
-        with open('/tmp/intermediate.pem') as f:
+        with open('{}/intermediate.pem'.format(tempfile.gettempdir())) as f:
             certificate_chain = f.read()
 
         if not manual:
@@ -114,8 +127,8 @@ def get_cert_and_update_domain(
 def create_domain_key():
     """
     """
-    proc = subprocess.Popen(
-        ["openssl genrsa 2048 > /tmp/domain.key"],
+    proc = Popen(
+        ['openssl genrsa 2048 > "{}/domain.key"'.format(tempfile.gettempdir())],
         stdin=subprocess.PIPE,
         stdout=subprocess.PIPE,
         stderr=subprocess.PIPE,
@@ -129,8 +142,8 @@ def create_domain_key():
 
 def create_domain_csr(domain):
     subj = "/CN=" + domain
-    cmd = 'openssl req -new -sha256 -key /tmp/domain.key -subj "{}"  > /tmp/domain.csr'.format(subj)
-    proc = subprocess.Popen(
+    cmd = 'openssl req -new -sha256 -key "{0}/domain.key" -subj "{1}"  > "{0}/domain.csr"'.format(tempfile.gettempdir(), subj)
+    proc = Popen(
         [cmd],
         stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
     )
@@ -143,11 +156,11 @@ def create_domain_csr(domain):
 def create_chained_certificate():
     cross_cert_url = "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem"
     cert = requests.get(cross_cert_url)
-    with open('/tmp/intermediate.pem', 'wb') as intermediate_pem:
+    with open('{}/intermediate.pem'.format(tempfile.gettempdir()), 'wb') as intermediate_pem:
         intermediate_pem.write(cert.content)
 
-    proc = subprocess.Popen(
-        ["cat /tmp/signed.crt /tmp/intermediate.pem > /tmp/chained.pem"],
+    proc = Popen(
+        ['cat "{0}/signed.crt" "{0}/intermediate.pem" > "{0}/chained.pem"'.format(tempfile.gettempdir())],
         stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
     )
     out, err = proc.communicate()
@@ -160,8 +173,8 @@ def create_chained_certificate():
 def parse_account_key():
     """Parse account key to get public key"""
     LOGGER.info("Parsing account key...")
-    proc = subprocess.Popen(
-        ["openssl rsa -in /tmp/account.key -noout -text"],
+    proc = Popen(
+        ['openssl rsa -in "{}/account.key" -noout -text'.format(tempfile.gettempdir())],
         stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
     )
     out, err = proc.communicate()
@@ -176,8 +189,8 @@ def parse_csr():
     Parse certificate signing request for domains
     """
     LOGGER.info("Parsing CSR...")
-    csr_filename = '/tmp/domain.csr'
-    proc = subprocess.Popen(
+    csr_filename = '{}/domain.csr'.format(tempfile.gettempdir())
+    proc = Popen(
         ["openssl req -in {} -noout -text".format(csr_filename)],
         stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
     )
@@ -331,8 +344,8 @@ def sign_certificate():
 
     """
     LOGGER.info("Signing certificate...")
-    proc = subprocess.Popen(
-        ["openssl req -in /tmp/domain.csr -outform DER"],
+    proc = Popen(
+        ['openssl req -in "{}/domain.csr" -outform DER'.format(tempfile.gettempdir())],
         stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
     )
     csr_der, err = proc.communicate()
@@ -353,7 +366,7 @@ def encode_certificate(result):
     """
     cert_body = """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format(
         "\n".join(textwrap.wrap(base64.b64encode(result).decode('utf8'), 64)))
-    signed_crt = open("/tmp/signed.crt", "w")
+    signed_crt = open("{}/signed.crt".format(tempfile.gettempdir()), "w")
     signed_crt.write(cert_body)
     signed_crt.close()
 
@@ -383,8 +396,8 @@ def _send_signed_request(url, payload):
     protected = copy.deepcopy(header)
     protected["nonce"] = urlopen(DEFAULT_CA + "/directory").headers['Replay-Nonce']
     protected64 = _b64(json.dumps(protected).encode('utf8'))
-    proc = subprocess.Popen(
-        ["openssl dgst -sha256 -sign /tmp/account.key"],
+    proc = Popen(
+        ['openssl dgst -sha256 -sign "{}/account.key"'.format(tempfile.gettempdir())],
         stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
     )
     out, err = proc.communicate("{0}.{1}".format(protected64, payload64).encode('utf8'))
@@ -410,14 +423,14 @@ def cleanup():
     Delete any temporary files.
     """
     filenames = [
-        '/tmp/account.key',
-        '/tmp/domain.key',
-        '/tmp/key.key',
-        '/tmp/domain.csr',
-        '/tmp/signed.crt',
-        '/tmp/intermediate.pem',
-        '/tmp/chained.pem',
-        '/tmp/lets-encrypt-x3-cross-signed.pem'
+        '{}/account.key'.format(tempfile.gettempdir()),
+        '{}/domain.key'.format(tempfile.gettempdir()),
+        '{}/key.key'.format(tempfile.gettempdir()),
+        '{}/domain.csr'.format(tempfile.gettempdir()),
+        '{}/signed.crt'.format(tempfile.gettempdir()),
+        '{}/intermediate.pem'.format(tempfile.gettempdir()),
+        '{}/chained.pem'.format(tempfile.gettempdir()),
+        '{}/lets-encrypt-x3-cross-signed.pem'.format(tempfile.gettempdir())
     ]
 
     for filename in filenames:


Test patch: