Ansible中如何遍历字典数组验证多域名证书与密钥匹配?
Ansible批量验证证书公钥匹配及过期状态问题解决
我在Ansible数据操作上经常卡壳,不太熟悉Python和字典对象,下面这个批量验证证书的例子能概括我的困惑:
需要验证一批证书,官方文档只有单域名示例,现在要遍历多个域名做验证,证书存储目录结构如下:
certs/ ├── domain.com │ ├── domain.com.pem │ └── domain.com.key └── domain.org ├── domain.org.key └── domain.org.pem
我写的Playbook如下:
--- - name: "check certs" hosts: localhost gather_facts: no vars: domain_names: - domain.com - domain.org certs_folder: certs tasks: - name: Get certificate information community.crypto.x509_certificate_info: path: "{{ certs_folder }}/{{ item }}/{{ item }}.pem" # for valid_at, invalid_at and valid_in register: result_certs loop: "{{ domain_names }}" failed_when: 0 - name: Get private key information community.crypto.openssl_privatekey_info: path: "{{ certs_folder }}/{{ item }}/{{ item }}.key" register: result_privatekey loop: "{{ domain_names }}" failed_when: 0 - name: Check cert and key match << DOES NOT WORK >>> assert: that: - result_certs[ {{ item }} ].public_key == result_privatekey[ {{ item }} ].public_key # - ... other checks ... - not result[ {{ item }} ].expired loop: "{{ domain_names }}"
执行后得到的result_certs和result_privatekey变量,其results字段是字典数组,结构如下:
"result_certs": { "changed": false, "msg": "All items completed", "results": [ { "expired": false, "item": "domain.org", "public_key": "<<PUBLIC KEY>>" }, { "expired": false, "item": "domain.com", "public_key": "<<PUBLIC KEY>>" } ], "skipped": false }
"result_privatekey": { "changed": false, "msg": "All items completed", "results": [ { "item": "domain.org", "public_key": "<< PUBLIC KEY >>" }, { "item": "domain.com", "public_key": "<< PUBLIC KEY >>" } ], "skipped": false }
我需要在assert任务中引用类似result_privatekey.results[item='domain.org'的字典].public_key的元素,来验证证书与密钥的公钥一致,以及证书未过期。我知道map和selectattr过滤器可能有用,但文档不够清晰,需要具体示例。
解决方案
方法1:直接用selectattr+first过滤器匹配域名
在assert任务中,通过selectattr('item', 'equalto', item)从results数组中筛选出当前域名对应的字典,再用first取第一个匹配结果(因为每个域名只会对应一条结果),之后就能正常读取字段了。
修改后的assert任务如下:
- name: Check cert and key match assert: that: - (result_certs.results | selectattr('item', 'equalto', item) | first).public_key == (result_privatekey.results | selectattr('item', 'equalto', item) | first).public_key - not (result_certs.results | selectattr('item', 'equalto', item) | first).expired fail_msg: "Domain {{ item }}: certificate validation failed" success_msg: "Domain {{ item }}: certificate is valid and matches private key" loop: "{{ domain_names }}"
方法2:提前将results转换为字典(更简洁)
如果觉得每次都写过滤器麻烦,可以先把results数组转换成以域名(item值)为键的字典,后续直接通过域名索引取值。
添加一个任务来生成转换后的字典:
- name: Transform results into domain-keyed dictionaries set_fact: certs_info_dict: "{{ result_certs.results | items2dict(key_name='item', value_name='') }}" privatekeys_info_dict: "{{ result_privatekey.results | items2dict(key_name='item', value_name='') }}"
之后的assert任务就能简化成:
- name: Check cert and key match (using dictionaries) assert: that: - certs_info_dict[item].public_key == privatekeys_info_dict[item].public_key - not certs_info_dict[item].expired fail_msg: "Domain {{ item }}: certificate validation failed" success_msg: "Domain {{ item }}: certificate is valid and matches private key" loop: "{{ domain_names }}"
说明:items2dict过滤器会把数组里的每个字典,以指定的key_name字段值作为键,整个字典作为值,生成新的字典,这样直接用域名索引就能拿到对应的数据,写法更直观。
内容的提问来源于stack exchange,提问作者jeanlabab
相关产品推荐
相关产品推荐

