ansible-collections / community.crypto

The community.crypto collection for Ansible.
https://galaxy.ansible.com/ui/repo/published/community/crypto/
Other
96 stars 85 forks source link

Unable to create ACME account with EAB #614

Closed withjigs closed 9 months ago

withjigs commented 1 year ago
SUMMARY

Unable to create ACME account using 'acme_account' module. Our company uses Keyfactor ACME solution, which requires External Account binding. I am able to create account using certbot acme client.

ISSUE TYPE
COMPONENT NAME

Module: community.crypto.acme_account

ANSIBLE VERSION
$ ansible --version
ansible [core 2.12.5]
  config file = None
  configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/user/ansible/lib/python3.8/site-packages/ansible
  ansible collection location = /home/user/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/user/ansible/bin/ansible
  python version = 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0]
  jinja version = 3.1.2
  libyaml = True
COLLECTION VERSION
$ ansible-galaxy collection list community.crypto

# /home/user/.ansible/collections/ansible_collections
Collection       Version
---------------- -------
community.crypto 2.5.0
CONFIGURATION
I get a blank output. 
OS / ENVIRONMENT
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.5 LTS"
STEPS TO REPRODUCE

Note: We are using Keyfactor ACME server. Create following playbook:

- name: ACME EAB
  hosts: localhost
  gather_facts: no

  vars:
    acme_directory_url: "my_acme_URL"
    acme_internal_eab_kid: "my_kid"
    acme_internal_eab_hmac_key: "my_hmac_key"

    private_key: |
        -----BEGIN RSA PRIVATE KEY-----
        my_account_private_key
        -----END RSA PRIVATE KEY-----

  tasks:

     - name: Ensure ACME account exists
       community.crypto.acme_account:
         acme_directory: "{{ acme_directory_url }}"
         acme_version: 2
         account_key_content: "{{ private_key }}"
         state: present
         contact:
           - me@company.com
         terms_agreed: true
         external_account_binding:
           kid: "{{ acme_internal_eab_kid }}"
           alg: "HS256"
           key: "{{ acme_internal_eab_hmac_key }}"

Run command:

ansible-playbook -vvv acme_eab.yml
EXPECTED RESULTS

ACME Account is created.

ACTUAL RESULTS

Get following error:

< TASK [Ensure ACME account exists] >
 -----------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

task path: /home/user/projects/acme/acme_eab.yml:42
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: user
<127.0.0.1> EXEC /bin/sh -c 'echo ~user && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/user/.ansible/tmp `"&& mkdir "` echo /home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206 `" && echo ansible-tmp-1685881672.744762-6999-19923614255206="` echo /home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206 `" ) && sleep 0'
Using module file /home/user/.ansible/collections/ansible_collections/community/crypto/plugins/modules/acme_account.py
<127.0.0.1> PUT /home/user/.ansible/tmp/ansible-local-6989hie4ijzs/tmpen1vmx2a TO /home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/AnsiballZ_acme_account.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/ /home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/AnsiballZ_acme_account.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/home/user/ansible/bin/python /home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/AnsiballZ_acme_account.py && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/AnsiballZ_acme_account.py", line 259, in <module>
    _ansiballz_main()
  File "/home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/AnsiballZ_acme_account.py", line 249, in _ansiballz_main
    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
  File "/home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/AnsiballZ_acme_account.py", line 122, in invoke_module
    runpy.run_module(mod_name='ansible_collections.community.crypto.plugins.modules.acme_account', init_globals=dict(_module_fqn='ansible_collections.community.crypto.plugins.modules.acme_account', _modlib_path=modlib_path),
  File "/usr/lib/python3.8/runpy.py", line 207, in run_module
    return _run_module_code(code, init_globals, run_name, mod_spec)
  File "/usr/lib/python3.8/runpy.py", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/modules/acme_account.py", line 339, in <module>
  File "/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/modules/acme_account.py", line 259, in main
  File "/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/module_utils/acme/account.py", line 208, in setup_account
  File "/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/module_utils/acme/account.py", line 67, in _new_reg
  File "/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/module_utils/acme/account.py", line 107, in _new_reg
AttributeError: 'bytes' object has no attribute 'get'
fatal: [localhost]: FAILED! => {
    "changed": false,
    "module_stderr": "Traceback (most recent call last):\n  File \"/home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/AnsiballZ_acme_account.py\", line 259, in <module>\n    _ansiballz_main()\n  File \"/home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/AnsiballZ_acme_account.py\", line 249, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/home/user/.ansible/tmp/ansible-tmp-1685881672.744762-6999-19923614255206/AnsiballZ_acme_account.py\", line 122, in invoke_module\n    runpy.run_module(mod_name='ansible_collections.community.crypto.plugins.modules.acme_account', init_globals=dict(_module_fqn='ansible_collections.community.crypto.plugins.modules.acme_account', _modlib_path=modlib_path),\n  File \"/usr/lib/python3.8/runpy.py\", line 207, in run_module\n    return _run_module_code(code, init_globals, run_name, mod_spec)\n  File \"/usr/lib/python3.8/runpy.py\", line 97, in _run_module_code\n    _run_code(code, mod_globals, init_globals,\n  File \"/usr/lib/python3.8/runpy.py\", line 87, in _run_code\n    exec(code, run_globals)\n  File \"/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/modules/acme_account.py\", line 339, in <module>\n  File \"/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/modules/acme_account.py\", line 259, in main\n  File \"/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/module_utils/acme/account.py\", line 208, in setup_account\n  File \"/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/module_utils/acme/account.py\", line 67, in _new_reg\n  File \"/tmp/ansible_community.crypto.acme_account_payload_fg6ymfez/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/module_utils/acme/account.py\", line 107, in _new_reg\nAttributeError: 'bytes' object has no attribute 'get'\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
    "rc": 1
}
felixfontein commented 1 year ago

From looking at what the code does, it sounds to me like the Keyfactor ACME server is implementing RFC 8555 incorrectly. https://www.rfc-editor.org/rfc/rfc8555#section-7.3.1 clearly specifies that a newAccount call which returns 200 must return the account object in JSON:

The body of this response represents the account object as it existed on the server before this request;

Apparently your server returned something that isn't JSON, and thus not what it should be.

(It also seems that the ACME account already exists, otherwise the server should not return 200.)

felixfontein commented 1 year ago

@dwandro since you authored https://github.com/dwandro/community.crypto/commit/26b63ff1e9cafb6b893ec99cfb37ffc2285d597f which references this issue: does that commit fix this problem?

Flybro commented 9 months ago

I have exactly the same problem, but with Digicert. Every other ACME client works fine, e.g. acme.sh, Lego, certbot, my own client written in Go, but I can't get it to work with Ansible.

Every time I get the following message:

The full traceback is:
  File "/var/folders/6j/h_b8qrh96yl4knvyql354h3h0000gn/T/ansible_community.crypto.acme_account_payload_s71aono2/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/modules/acme_account.py", line 265, in main
  File "/var/folders/6j/h_b8qrh96yl4knvyql354h3h0000gn/T/ansible_community.crypto.acme_account_payload_s71aono2/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/module_utils/acme/account.py", line 208, in setup_account
    created, account_data = self._new_reg(
                            ^^^^^^^^^^^^^^
  File "/var/folders/6j/h_b8qrh96yl4knvyql354h3h0000gn/T/ansible_community.crypto.acme_account_payload_s71aono2/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/module_utils/acme/account.py", line 67, in _new_reg
    created, data = self._new_reg(contact=contact, allow_creation=False)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/folders/6j/h_b8qrh96yl4knvyql354h3h0000gn/T/ansible_community.crypto.acme_account_payload_s71aono2/ansible_community.crypto.acme_account_payload.zip/ansible_collections/community/crypto/plugins/module_utils/acme/account.py", line 133, in _new_reg
    raise ACMEProtocolException(
fatal: [localhost]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "account_key_content": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "account_key_passphrase": null,
            "account_key_src": null,
            "account_uri": null,
            "acme_directory": "https://one.digicert.com/mpki/api/v1/acme/v2/directory",
            "acme_version": 2,
            "allow_creation": true,
            "contact": [
                "mailto:\"me@example.com\""
            ],
            "external_account_binding": {
                "alg": "HS256",
                "key": "********==",
                "kid": "A_3o1KCficGkNrp6DgA_1aircj3BsLFTxD63zVqFR2Q"
            },
            "new_account_key_content": null,
            "new_account_key_passphrase": null,
            "new_account_key_src": null,
            "request_timeout": 60,
            "select_crypto_backend": "auto",
            "state": "present",
            "terms_agreed": true,
            "validate_certs": true
        }
    },
    "msg": "Registering ACME account failed for https://one.digicert.com/mpki/api/v1/acme/v2/new-account with status 404 Not Found. Error urn:ietf:params:acme:error:accountDoesNotExist: \"ACME account not Found.\".",
    "other": {
        "http_status": 404,
        "http_url": "https://one.digicert.com/mpki/api/v1/acme/v2/new-account",
        "problem": {
            "detail": "ACME account not Found.",
            "type": "urn:ietf:params:acme:error:accountDoesNotExist"
        },
        "subproblems": []
    }
}
felixfontein commented 9 months ago

@Flybro do you know whether there is a testing/staging version of the Digicert CA that can be used for free? If yes, I can try debugging this. If it requires a paid account, I won't be able to do that.

Since you seem to have experience with developing ACME clients and have access to Digicert's ACME endpoint, you could also try to debug this.

Flybro commented 9 months ago

@felixfontein, thank you for your efforts. If you like, I can send you the KID and KEY and a minimal example that I have used here. Give me an address where I can send them and I will do it immediately.

Sorry, but when it comes to Python, my debugging skills are rather poor.

felixfontein commented 9 months ago

If you are comfortable doing this (this has some security implications on your end!) you can send it to the email address in https://github.com/ansible-collections/community.crypto/blob/main/plugins/modules/crypto_info.py#L4.

Flybro commented 9 months ago

I have sent you an email explaining what, how and why. Thank you very much for your help. I will try to get in touch with my colleagues around me who are well versed in Python and together we will try to investigate the problem further. If we find something, I will let you know.

felixfontein commented 9 months ago

Thanks, that additional information and debug output helped! #681 fixes this issue.