Repository: ansible/ansible-modules-extras

Issue:
Certificate expiration date parsing fails in letsencrypt module
<!---
Please do not report issues/requests related to Ansible modules here !!

Report them to the appropriate modules-core or modules-extras project:
  - https://github.com/ansible/ansible-modules-core/issues
  - https://github.com/ansible/ansible-modules-extras/issues

Also verify first that your issue/request is not already reported in GitHub
-->
##### ISSUE TYPE
- Bug report
##### COMPONENT NAME

letsencrypt

<!--- Name of the plugin/task/feature -->
##### ANSIBLE VERSION

<!--- Paste verbatim output from “ansible --version” between quotes below -->

```
ansible 2.2.0.0
  config file = 
  configured module search path = Default w/o overrides
```

Installed from rc1 tarball `sudo python setup.py install` after purging previous .deb installation.
##### CONFIGURATION

<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

Default everything
##### OS / ENVIRONMENT

<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->

Ubuntu 16.04, python 2.7, managed platform centos7
##### SUMMARY

<!--- Explain the problem briefly -->

letsencrypt module fails to parse dates from generated certificate I
##### STEPS TO REPRODUCE

letsencrypt module manages to create a certificate:

```

---
- hosts: somegroup
  tasks:
    - name: challenge
      letsencrypt:
        account_key: /etc/ssl/letsencrypt/letsencrypt2.key
        csr: /etc/ssl/letsencrypt/letsencrypt2.csr
        dest: /etc/ssl/letsencrypt/letsencrypt-temp.crt
      register: le_challenge

    - name: create challenge data
      copy:      
        dest: "/usr/share/nginx/html/{{ le_challenge['challenge_data']['example.org']['http-01']['resource'] }}"
        content: "{{ le_challenge['challenge_data']['example.org']['http-01']['resource_value'] }}"
      when: le_challenge|changed

    - name: create cert
      letsencrypt:
        account_key: /etc/ssl/letsencrypt/letsencrypt2.key
        csr: /etc/ssl/letsencrypt/letsencrypt2.csr
        dest: /etc/ssl/letsencrypt/letsencrypt-temp.crt
        data: "{{ le_challenge }}"
```

With above configuration the current result is:

```
FAILED! => {"changed": false, "failed": true, "msg": "No 'Not after' date found in /etc/ssl/letsencrypt/letsencrypt-temp.crt"}
```

If i open the cert like the module does, I can see:

```
$ openssl x509 -in letsencrypt-temp.crt -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            fa:5b:41:27:f9:65:85:43:9e:dc:b3:bd:a2:d1:45:6f:d0:3e
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=Fake LE Intermediate X1
        Validity
            Not Before: Oct 13 04:30:00 2016 GMT
            Not After : Jan 11 04:30:00 2017 GMT
        Subject: CN=example.org
        ... etc
```

Additionally, the following snippet works fine:

```
import re
import time
from datetime import datetime

src = """Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            fa:5b:41:27:f9:65:85:43:9e:dc:b3:bd:a2:d1:45:6f:d0:3e
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=Fake LE Intermediate X1
        Validity
            Not Before: Oct 13 04:30:00 2016 GMT
            Not After : Jan 11 04:30:00 2017 GMT
        Subject: CN=example.org"""

try:
    not_after_str = re.search(r"\s+Not After\s*:\s+(.*)", src).group(1)
    not_after = datetime.fromtimestamp(time.mktime(time.strptime(not_after_str,'%b %d %H:%M:%S %Y %Z')))
except ValueError as ve:
    print ve
print not_after
```

```
$ python test.py 
2017-01-11 04:30:00
```



Discussion:
<!-- @ptorkko: Thanks for filing this bug report! -->

@mgruener ping, this issue is waiting for your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_bug_new --->

Updated to RC4, reset everything related to this on the server and tried again.

Works fine until step3 in the original example:

`FAILED! => {"changed": false, "failed": true, "msg": "Faild to parse 'Not after' date of /etc/ssl/letsencrypt/le********.crt"}`

```
[root@example letsencrypt]# openssl x509 -in le2.crt  -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            fa:8f:c8:03:fa:f8:8f:06:1a:91:e7:da:c9:bf:63:f3:c8:ac
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=Fake LE Intermediate X1
        Validity
            Not Before: Oct 28 09:29:00 2016 GMT
            Not After : Jan 26 09:29:00 2017 GMT
        Subject: CN=example.org
```

Any ideas, I'd rather not spend time implementing letsencrypt some other way if this module is supposed to exist in 2.2.

Did some hackery with my limited python skills, and the error seems to be: 

`'Jan 26 09:29:00 2017 GMT' does not match format '%b %d %H:%M:%S %Y %Z'`

In a python console on the target server i get:

```
>>> import time
>>> print time.strftime("%b %d %H:%M:%S %Y %Z")
Oct 28 10:55:32 2016 UTC
```

So the locale is not an issue, and I'm stumped.

edit:

Additionally:

```
>>> print time.strptime("Jan 26 09:29:00 2017 GMT", "%b %d %H:%M:%S %Y %Z")
time.struct_time(tm_year=2017, tm_mon=1, tm_mday=26, tm_hour=9, tm_min=29, tm_sec=0, tm_wday=3, tm_yday=26, tm_isdst=0)
```


Resolving patch:
diff --git a/web_infrastructure/letsencrypt.py b/web_infrastructure/letsencrypt.py
--- a/web_infrastructure/letsencrypt.py
+++ b/web_infrastructure/letsencrypt.py
@@ -20,6 +20,7 @@
 
 import binascii
 import copy
+import locale
 import textwrap
 from datetime import datetime
 
@@ -767,6 +768,9 @@ def main():
         ),
         supports_check_mode = True,
     )
+ 
+    # AnsibleModule() changes the locale, so change it back to C because we rely on time.strptime() when parsing certificate dates.
+    locale.setlocale(locale.LC_ALL, "C")
 
     cert_days = get_cert_days(module,module.params['dest'])
     if cert_days < module.params['remaining_days']:


Test patch: