saltstack / salt

Software to automate the management and configuration of any infrastructure or application at scale. Get access to the Salt software package repository here:
https://repo.saltproject.io/
Apache License 2.0
14k stars 5.47k forks source link

[BUG] x509_v2 certificate_managed is passing newline stripped data to append_certs #66464

Open mcd1992 opened 2 months ago

mcd1992 commented 2 months ago

Description x509_v2 certificate_managed append_certs parameter is stripping newlines from PEM certificates.

When using the below state the minion will throw a invalid index error in salt/utils/x509.py:814 because the pems = split_pems(cert) is receiving PEM pillar data with its newlines replaced with spaces. On the master split_pems receives proper pillar data with the newlines included. But the minion seems to have newlines replaced with spaces.

For now as a workaround I've just changed the append_certs to run the pillar data into base64_encode which works.

Setup

  x509.certificate_managed:
    - name: /etc/pki/fqdn/public.crt.pem
    - ca_server: master.local
    - signing_policy: encipher
    - private_key: /etc/pki/fqdn/private.key.pem
    - CN: fqdn
    - days_valid: 90
    - days_remaining: 60
    - makedirs: True
    - dir_mode: 0700
    - mode: 0600
    - subjectAltName: 'DNS: fqdn'
    - encoding: pem
    - append_certs:
      - {{pillar['cacerts']['int_ca']}}
      - {{pillar['cacerts']['root_ca']}}

Versions Report

Salt Version:
          Salt: 3007.0

Python Version:
        Python: 3.12.3 (main, Apr 23 2024, 09:16:07) [GCC 13.2.1 20240417]

Dependency Versions:
          cffi: 1.16.0
      cherrypy: Not Installed
      dateutil: Not Installed
     docker-py: Not Installed
         gitdb: Not Installed
     gitpython: Not Installed
        Jinja2: 3.1.3
       libgit2: Not Installed
  looseversion: 1.3.0
      M2Crypto: 0.40.1
          Mako: Not Installed
       msgpack: 1.0.5
  msgpack-pure: Not Installed
  mysql-python: Not Installed
     packaging: 23.2
     pycparser: 2.22
      pycrypto: Not Installed
  pycryptodome: 3.12.0
        pygit2: Not Installed
  python-gnupg: Not Installed
        PyYAML: 6.0.1
         PyZMQ: 25.1.2
        relenv: Not Installed
         smmap: Not Installed
       timelib: Not Installed
       Tornado: 6.4
           ZMQ: 4.3.5

Salt Package Information:
  Package Type: Not Installed

System Versions:
          dist: arch  
        locale: utf-8
       machine: x86_64
       release: 6.5.11-8-pve
        system: Linux
       version: Arch Linux  

Both master and minion are Arch Linux on 3007.0

lkubb commented 2 months ago

Without knowledge about the specifics of your setup, I suspect the title does not describe the problem correctly. It should probably be utils.x509.load_cert crashes when confronted with improperly formatted PEM data. In general, load_cert should definitely not cause a traceback, while split_pems should probably work with improperly formatted data.

An initial reaction:

It's generally recommended to dump data (especially strings that can contain newlines) by passing it through the json filter:

# ...
    - append_certs:
      - {{ pillar['cacerts']['int_ca'] | json }}
      - {{ pillar['cacerts']['root_ca'] | json }}

Otherwise, the rendered YAML can become garbled/invalid (+ you might open yourself up to a template injection attack, depending on the source of the data).

Since your template seems to render fine (when I skip |json, the YAML is invalid), I assume the pillar values do not contain the properly formatted certificates verbatim (including newlines). The split_pems function does not fix those though, it expects

-----BEGIN CERTIFICATE-----
MII...
...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MII...
...
-----END CERTIFICATE-----
...

and strips anything that's not in between \n?-----BEGIN (CERTIFICATE|...)-----\n[...]\n-----END (CERTIFICATE|...)-----\n?

If the certificates in the pillar are read from a YAML file, they should be defined like:

cacerts:
  int_ca: |
    -----BEGIN CERTIFICATE-----
    MII...
    ...
    -----END CERTIFICATE-----
  root_ca: |
    -----BEGIN CERTIFICATE-----
    MII...
    ...
    -----END CERTIFICATE-----
mcd1992 commented 2 months ago

My pillar data is defined just like that, with the multline |. And prior to migrating to x509_v2 this worked without | json or | base64_encode.

What's weird is when I patch utils/x509.py:load_cert on the minion and master, to have a debug print for the cert variable. The master gets a proper PEM string with literal \ns (not 0x0a) in it but the minion gets spaces or some sort of whitespace instead of \n.

When I do salt-call pillar.data on minion and master they both show the proper PEM with newlines. I'll try to replicate with 2 docker containers since I can't replicate with a simple 'masterless' setup.

lkubb commented 2 months ago

That's weird. Does | json fix it (I doubt it)? If not, you can try salt["x509.get_pem_entry"](pillar['cacerts']['int_ca']) | json.

I can attest that the previous x509 modules ran the certificates that were specified in append_certs through x509.get_pem_entry, which fixes improperly formatted PEM entries. Fixing it in the state module is not an option for x509_v2 since it also supports other input types there (base64/hex-encoded data), we would have to adapt the split_pems function.

mcd1992 commented 2 months ago

| json does workaround just like | base64_encode

I'll try and get a minimum reproducible setup going for debugging. It may be related somehow to this x509.certificate_managed state being inside a macro as well.

lkubb commented 2 months ago

Awesome. Yes, that's a likely culprit.

| json is not a workaround per se, it is how multiline strings should always be escaped, documented (not in the best location) here: https://github.com/saltstack/salt/blob/cf6c1e10687409519c478ac2594b9f16b9afc5be/salt/states/x509_v2.py#L134-L150

If omitted, the resulting YAML should be unparsable:

[salt.state       :4326][CRITICAL][746574] Rendering SLS 'base:x509test' failed: could not find expected ':'; line 17

---
[...]
    - subjectAltName: 'DNS: fqdn'
    - encoding: pem
    - append_certs:
      - -----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIIbfpgqP0VGPgwDQYJKoZIhvcNAQELBQAwKzELMAkGA1UE
BhMCVVMxDTALBgNVBAMMBFRlc3QxDTALBgNVBAoMBFNhbHQwHhcNMjIxMTE1MTQw    <======================