Having a large number of iDRAC out of band (OOB) interfaces to configure on Dell hardware can seem like a mind numbing daunting task to administrators everywhere. Recently my team was tasked with the following basic tasks on around 80 iDRACs:
- Hostname
- DNS Setting
- Syslog Settings
- LDAP configuration & RBAC mapping
- SSL Certificate update (interaction with a Microsoft PKI server (certsrv)
- NTP configuration
- Update root credential and place into Hashicorp Vault
After a little research and fiddling with APIs / RACADM and the GUI I settled upon using an Ansible module ‘idrac_redfish_config‘
Without question its the easiest way to configure the bulk of components within iDRAC. ** Certain elements are not possible with this module – I will cover this later **
To explain how to actually use the module, take this task as an example:
- name: Configure NTP and Timezone on iDRAC idrac_redfish_config: category: Manager command: SetManagerAttributes manager_attributes: NTPConfigGroup.1.NTPEnable: "Enabled" NTPConfigGroup.1.NTP1: "{{ ntp_server }}" Time.1.Timezone: "{{ timezone }}" baseuri: "{{ idrac_ip }}" username: "{{ default_user }}" password: "{{ idrac_pass }}"
The manager_attributes section is the key to this module, and typically it is poorly described within the Ansible documentation. The best way to obtain these attributes is to open an iDRAC GUI up, hit F12 to developer mode and track the network activity when saving a change. An attributes POST action includes the payload in the correct format for inclusion in this Ansible task.
My basic playbook for configuration is as follows (excluding certificate change).
--- - name: Configure NTP and Timezone on iDRAC idrac_redfish_config: category: Manager command: SetManagerAttributes manager_attributes: NTPConfigGroup.1.NTPEnable: "Enabled" NTPConfigGroup.1.NTP1: "{{ ntp_server }}" Time.1.Timezone: "{{ timezone }}" baseuri: "{{ idrac_ip }}" username: "{{ default_user }}" password: "{{ idrac_pass }}" - name: Configure DNS Servers idrac_redfish_config: category: Manager command: SetManagerAttributes manager_attributes: IPv4Static.1.DNS1: "{{ dns_server.primary }}" IPv4Static.1.DNS2: "{{ dns_server.secondary }}" baseuri: "{{ idrac_ip }}" username: "{{ default_user }}" password: "{{ idrac_pass }}" - name: Configure iDRAC Name and domain idrac_redfish_config: category: Manager command: SetManagerAttributes manager_attributes: NIC.1.DNSDomainName: "{{ domain_name }}" NIC.1.DNSRacName: "{{ idrac_hostname }}" NIC.1.DNSRegister: "Enabled" baseuri: "{{ idrac_ip }}" username: "{{ default_user }}" password: "{{ idrac_pass }}" - name: Configure TLS 1.2 idrac_redfish_config: category: Manager command: SetManagerAttributes manager_attributes: WebServer.1.TLSProtocol: "TLS 1.2 Only" NIC.1.DNSRacName: "{{ idrac_hostname }}" NIC.1.DNSRegister: "Enabled" baseuri: "{{ idrac_ip }}" username: "{{ default_user }}" password: "{{ idrac_pass }}" - name: Configure LDAPS idrac_redfish_config: category: Manager command: SetManagerAttributes manager_attributes: LDAP.1.Enable: "Enabled" LDAPRoleGroup.1.DN: "CN=DG_IDRAC_ADMINS,OU=1_IDRAC,OU=blah,DC=blah" LDAPRoleGroup.1.Privilege: 511 LDAPRoleGroup.2.DN: "CN=DG_IDRAC_READ,OU=1_IDRAC,OU=blah,DC=blah" LDAPRoleGroup.2.Privilege: 1 LDAP.1.BaseDN: "{{ base_dn }}" LDAP.1.BindDN: "{{ bind_account_name }},{{ base_dn }}" LDAP.1.BindPassword: "{{ ldap_bind_pwd }}" LDAP.1.CertValidationEnable: "Disabled" LDAP.1.Enable: "Enabled" LDAP.1.GroupAttribute: "" LDAP.1.GroupAttributeIsDN: "Enabled" LDAP.1.Port: 636 LDAP.1.SearchFilter: "" LDAP.1.Server: "{{ domain_name }}" LDAP.1.UserAttribute: "sAMAccountName" baseuri: "{{ idrac_ip }}" username: "{{ default_user }}" password: "{{ idrac_pass }}" - name: Configure Syslog idrac_redfish_config: category: Manager command: SetManagerAttributes manager_attributes: SysLog.1.Port: 514 SysLog.1.Server1: 1.2.3.4 SysLog.1.SysLogEnable: "Enabled" baseuri: "{{ idrac_ip }}" username: "{{ default_user }}" password: "{{ idrac_pass }}" - name: Login to Vault (Root NS) as Service Account uri: url: "{{ vault_url }}/v1/auth/ldap/login/{{ vault_svc_acc_username }}" method: POST body_format: json body: {"password":"{{ vault_svc_acc_password }}"} return_content: yes follow_redirects: all status_code: 200 validate_certs: no register: data no_log: true - name: Fetch a new password from Vault uri: url: "{{ vault_url }}/v1/gen/password" method: POST body_format: json headers: X-Vault-Token: "{{ data.json.auth.client_token }}" body: {"length":"15", "digits":"3", "symbols":"3", "allow_uppercase":"true"} return_content: yes status_code: 200 validate_certs: no register: new_pass no_log: true - name: Login to Vault as Service Account uri: url: "{{ vault_url }}/v1/auth/ldap/login/{{ vault_svc_acc_username }}" method: POST body_format: json headers: X-Vault-Namespace: "{{ vault_namespace }}" body: {"password":"{{ vault_svc_acc_password }}"} return_content: yes status_code: 200 validate_certs: no register: ns_data no_log: true - name: Post new server record to Vault - iDRAC uri: url: "{{ vault_url }}/v1/data/servers/idrac/{{ idrac_hostname | lower}}/root" body_format: json method: POST headers: X-Vault-Token: "{{ ns_data.json.auth.client_token }}" X-Vault-Namespace: "{{ vault_namespace }}" body: { "options": { "max_versions": "12" }, "data": { "password":"{{ new_pass.json.data.value }}" }} return_content: yes status_code: 200 validate_certs: no no_log: true - name: Change Root Password idrac_redfish_config: category: Manager command: SetManagerAttributes manager_attributes: Users.2.AuthenticationProtocol: "SHA" Users.2.EmailAddress: "" Users.2.Enable: "Enabled" Users.2.IpmiLanPrivilege: "Administrator" Users.2.IpmiSerialPrivilege: "Administrator" Users.2.Password: "{{ new_pass.json.data.value }}" Users.2.PrivacyProtocol: "AES" Users.2.Privilege: 511 Users.2.ProtocolEnable: "Disabled" Users.2.Simple2FA: "Disabled" Users.2.SolEnable: "Enabled" Users.2.UseEmail: "Disabled" Users.2.UserName: "root" baseuri: "{{ idrac_ip }}" username: "{{ default_user }}" password: "{{ idrac_pass }}"
Certificates…
Of course changing the SSL certificate wouldn’t be as easy as all the elements above. The Ansible module does not support the changing of the certificate for some unknown reason, so I had to delve into the hell which is the Dell developer website, specifically for these poorly described API calls….
The flow for this playbook is as follows:
- Generate CSR on the iDRAC and export
- Send CSR to Microsoft PKI server and request against a specific template
- Obtain certificate for certsrv
- Parse cert and upload to iDRAC
- Reset iDRAC for cert to take effect
-- - name: Generate iDRAC CSR uri: url: "https://{{ idrac_ip }}/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR" method: POST url_username: "{{ default_user}}" url_password: "{{ idrac_pass }}" force_basic_auth: yes return_content: yes body_format: json status_code: 200 validate_certs: no body: '{ "Country" : "GB", "State" : "StateHere", "City" : "Cityhere", "Organization" : "Blah", "OrganizationalUnit" : "BlahOU", "CommonName" : "{{ idrac_hostname }}.{{ domain_name }}", "AlternativeNames" : [ "{{ idrac_hostname }}.{{ domain_name }}", "{{ idrac_hostname }}" ], "CertificateCollection" : { "@odata.id" : "/redfish/v1/Managers/iDRAC.Embedded.1/NetworkProtocol/HTTPS/Certificates" } }' no_log: false register: data - name: Set CSR as new fact set_fact: new_csr: "{{ data.json.CSRString | replace('\n','') }}" - name: Print out the CSR debug: var: new_csr - name: Send CSR to Microsoft PKI server uri: url: "https://{{ pki_server }}/certsrv/certfnsh.asp" method: POST url_username: "{{ pki_svc_account }}@{{ domain_name | upper }}" url_password: "{{ pki_pwd }}" use_gssapi: yes return_content: yes body_format: form-urlencoded status_code: 200 validate_certs: no body: Mode: "newreq" CertRequest: "{{ new_csr }}" CertAttrib: "CertificateTemplate:{{ pki_template }}" FriendlyType: "Saved-Request Certificate" TargetStoreFlags: "0" SaveCert: "yes" no_log: true register: cert_out - name: Set content as a fact set_fact: content: "{{ cert_out.content }}" - name: Push new fact to file copy: content: "{{ content }}" dest: ./content - name: Grab Request ID shell: pwsh -c "./regex_search.ps1" register: req_id - name: print req id debug: msg: "{{ req_id }}" - name: Obtain certificate from PKI server uri: url: "https://{{ pki_server }}/certsrv/certnew.cer?ReqID={{ req_id.stdout }}&Enc=b64" method: GET url_username: "{{ pki_svc_account }}@{{ domain_name | upper }}" url_password: "{{ pki_pwd }}" use_gssapi: yes return_content: yes status_code: 200 validate_certs: no no_log: true register: new_cert - name: Parse the new certificate to clear up the rubbish \r instances returned from MS set_fact: cert: "{{ new_cert.content | regex_replace('\\r?','') }}" - name: Print the new parsed certificate debug: var: cert - name: Upload Certificate to iDRAC uri: url: "https://{{ idrac_ip }}/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DelliDRACCardService/Actions/DelliDRACCardService.ImportSSLCertificate" method: POST url_username: "{{ default_user}}" url_password: "{{ idrac_pass }}" force_basic_auth: yes return_content: yes body_format: json status_code: 200 validate_certs: no body: '{ "SSLCertificateFile" : "{{ cert }}", "CertificateType" : "Server" }' no_log: true register: data - name: Reset IDRAC to allow the certificate take effect uri: url: "https://{{ idrac_ip }}/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Manager.Reset" method: POST url_username: "{{ default_user}}" url_password: "{{ idrac_pass }}" force_basic_auth: yes return_content: yes body_format: json status_code: 204 validate_certs: no body: '{ "ResetType" : "GracefulRestart" }' no_log: false - name: "Wait for {{ idrac_ip }} to come up" uri: url: "https://{{ idrac_ip }}//restgui/start.html" validate_certs: no method: GET follow_redirects: none register: _result until: _result.status == 200 retries: 30 delay: 20 # 5 minute wait time
For these interested, I have a small PowerShell hack in this code to grab the ReqID from the output from the PKI server which is….
I ran out of time to integrate this into a Ansible regex_search task, but I’m sure its possible!
regex_search.ps1 $reqID = (get-content ./content | select-string -Pattern "ReqID=[0-9]{1,5}" | select-object -Index 0).Matches.Value.Split("=")[1] write-host $reqID
I hope this helps someone out!!
Hi,
thank you for your inspirational post. I have found your post for obtaining Certificates through Microsoft PKI with Ansible. Here the Task which works for us with Regex within Ansible and without Powershell:
– name: “Find Request ID”
ansible.builtin.set_fact:
cert_reqid: “{{ cert_out.content | regex_search(‘ReqID=[0-9]{1,6}’) }}”
Our RegID is 6 digits instead 5. Maybe in future 7 digits. But that´s next construction site.