opiproject / ansible-opi-dpu

Ansible Modules for DPUs
Apache License 2.0
6 stars 8 forks source link

use session instead of login #34

Open glimchb opened 9 months ago

glimchb commented 9 months ago

see https://docs.ansible.com/ansible/latest/collections/community/general/redfish_command_module.html#examples

trying something like this:

- name: Create session
  community.general.redfish_command:
    category: Sessions
    command: CreateSession
    baseuri: "{{ inventory_hostname }}"
    username: "{{ dpu_bmc_username }}"
    password: "{{ dpu_bmc_password }}"
  delegate_to: localhost
  register: result_session

- name: Extract session details from login result
  ansible.builtin.set_fact:
    bmc_firmware_update_token: "{{ result_session.session.token }}"
    bmc_firmware_update_uri: "{{ result_session.session.uri }}"

- name: Print Session Tokens
  ansible.builtin.debug:
    msg:
      - "{{ bmc_firmware_update_token }}"
      - "{{ bmc_firmware_update_uri }}"

- name: Get Firmware Inventory
  community.general.redfish_info:
    category: Update
    command: GetFirmwareInventory
    baseuri: "{{ inventory_hostname }}"
    auth_token: "{{ bmc_firmware_update_token }}"
    # username: "{{ dpu_bmc_username }}"
    # password: "{{ dpu_bmc_password }}"
  register: result
  delegate_to: localhost

- name: Delete session using security token created by CreateSesssion above
  community.general.redfish_command:
    category: Sessions
    command: DeleteSession
    baseuri: "{{ inventory_hostname }}"
    auth_token: "{{ bmc_firmware_update_token }}"
    session_uri: "{{ bmc_firmware_update_uri }}"
  delegate_to: localhost

I get

2024-03-01 18:58:29     "msg": "Action was successful",
2024-03-01 18:58:29     "return_values": {},
2024-03-01 18:58:29     "session": {
2024-03-01 18:58:29         "token": "2M5740CIEQhDqLelfLSU",
2024-03-01 18:58:29         "uri": "/redfish/v1/SessionService/Sessions/EeMxklMJZo"
2024-03-01 18:58:29     }
2024-03-01 18:58:29 }

but fails on

2024-03-01 18:58:32 fatal: [10.246.87.133 -> localhost]: FAILED! => {
2024-03-01 18:58:32     "changed": false,
2024-03-01 18:58:32     "invocation": {
2024-03-01 18:58:32         "module_args": {
2024-03-01 18:58:32             "auth_token": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
2024-03-01 18:58:32             "baseuri": "10.246.87.133",
2024-03-01 18:58:32             "category": [
2024-03-01 18:58:32                 "Update"
2024-03-01 18:58:32             ],
2024-03-01 18:58:32             "command": [
2024-03-01 18:58:32                 "GetFirmwareInventory"
2024-03-01 18:58:32             ],
2024-03-01 18:58:32             "manager": null,
2024-03-01 18:58:32             "password": null,
2024-03-01 18:58:32             "timeout": null,
2024-03-01 18:58:32             "update_handle": null,
2024-03-01 18:58:32             "username": null
2024-03-01 18:58:32         }
2024-03-01 18:58:32     },
2024-03-01 18:58:32     "msg": "HTTP Error 401 on GET request to 'https://10.246.87.133/redfish/v1/UpdateService', extended message: 'While accessing the resource at '/redfish/v1/UpdateService', the service received an authorization error 'Invalid username or password'.'"
2024-03-01 18:58:32 }

curl seems working...

curl -k -H "X-Auth-Token: eFzrs6HNLSYtA0y0IczW" -X GET https://10.246.87.133/redfish/v1/UpdatService/FirmwareInventory
jcastanos2 commented 8 months ago
``Traceback (most recent call last):
  File "/usr/lib/python3.10/urllib/request.py", line 1348, in do_open
    h.request(req.get_method(), req.selector, req.data, headers,
  File "/usr/lib/python3.10/http/client.py", line 1283, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/usr/lib/python3.10/http/client.py", line 1329, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.10/http/client.py", line 1278, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.10/http/client.py", line 1038, in _send_output
    self.send(msg)
  File "/usr/lib/python3.10/http/client.py", line 976, in send
    self.connect()
  File "/usr/lib/python3.10/http/client.py", line 1455, in connect
    self.sock = self._context.wrap_socket(self.sock,
  File "/usr/lib/python3.10/ssl.py", line 513, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.10/ssl.py", line 1100, in _create
    self.do_handshake()
  File "/usr/lib/python3.10/ssl.py", line 1371, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed \
certificate (_ssl.c:1007)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/sancho/opi/ansible-opi-dpu/./jgctests/get.py", line 177, in <module>
    main()
  File "/home/sancho/opi/ansible-opi-dpu/./jgctests/get.py", line 174, in main
    utils.open_url("GET", "https://10.137.189.246/redfish/v1/UpdateService")
  File "/home/sancho/opi/ansible-opi-dpu/./jgctests/get.py", line 167, in open_url
    r = urllib_request.urlopen(request, None, timeout)
  File "/usr/lib/python3.10/urllib/request.py", line 216, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python3.10/urllib/request.py", line 519, in open
    response = self._open(req, data)
  File "/usr/lib/python3.10/urllib/request.py", line 536, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
  File "/usr/lib/python3.10/urllib/request.py", line 496, in _call_chain
    result = func(*args)
  File "/usr/lib/python3.10/urllib/request.py", line 1391, in https_open
    return self.do_open(http.client.HTTPSConnection, req,
  File "/usr/lib/python3.10/urllib/request.py", line 1351, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self\
-signed certificate (_ssl.c:1007)>

It can be fixed by making the following change in

/usr/lib/python3/dist-packages/ansible/module_utils/urls.py
def open(self, method, url, data=None, headers=None, use_proxy=None,
             force=None, last_mod_time=None, timeout=None, validate_certs=None,
             url_username=None, url_password=None, http_agent=None,
             force_basic_auth=None, follow_redirects=None,
             client_cert=None, client_key=None, cookies=None, use_gssapi=False,
             unix_socket=None, ca_path=None, unredirected_headers=None, decompress=None,
             ciphers=None, use_netrc=None):
...

        context = ssl._create_unverified_context()
        r = urllib_request.urlopen(request, None, timeout, context=context)

But likely this is not the right fix

glimchb commented 8 months ago

I think I have reproduction with basic python (3.6.8) and also enabled debug prints now:

this works (from https://github.com/DMTF/python-redfish-library/blob/main/src/redfish/rest/v1.py) :

import http.client
http.client.HTTPConnection.debuglevel = 1

import redfish
r = redfish.redfish_client(base_url='https://10.246.87.133')
for x in range(3):
    s = r.get('/redfish/v1/UpdateService', headers={'X-Auth-Token' : "iwKUR3no1WOHB5PFkd5H"})
    print(s.status)
    print(s.text)

and this works:

import http.client
http.client.HTTPConnection.debuglevel = 1

import requests
s = requests.Session()
for x in range(3):
    resp = s.request("GET", "https://10.246.87.133/redfish/v1/UpdateService", headers={'X-Auth-Token' : "iwKUR3no1WOHB5PFkd5H"}, verify=False)
    print(resp)

vs fails:

import ssl
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

import urllib.request
handler = urllib.request.HTTPSHandler(debuglevel=1, context=ctx)
opener = urllib.request.build_opener(handler)
opener.addheaders = [('X-Auth-Token', 'iwKUR3no1WOHB5PFkd5H')]
for x in range(3):
    response = opener.open("https://10.246.87.133/redfish/v1/UpdateService")
    print(response.read())

When it fails, first iteration is ok, second is failed (that's why range in the code) Also important, session object is deleted on the BMC side... Will debug more tomorrow...

working debug info:

send: b'GET /redfish/v1/UpdateService HTTP/1.1\r\nHost: 10.246.87.133\r\nUser-Agent: python-requests/2.27.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nX-Auth-Token: iwKUR3no1WOHB5PFkd5H\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: OData-Version: 4.0
header: Allow: GET, PATCH, HEAD
header: ETag: "2BB654F0"
header: Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
header: X-Frame-Options: DENY
header: Pragma: no-cache
header: Cache-Control: no-Store,no-Cache
header: X-XSS-Protection: 1; mode=block
header: X-Content-Type-Options: nosniff
header: Content-Security-Policy: default-src 'none'; img-src 'self' data:; font-src 'self'; style-src 'self'; script-src 'self'; connect-src 'self' wss:; form-action 'none'; frame-ancestors 'none'; object-src 'none'; base-uri 'none'
header: Content-Type: application/json
header: Date: Tue, 05 Mar 2024 03:54:39 GMT
header: Content-Length: 1821
<Response [200]>

failed debug info:

send: b'GET /redfish/v1/UpdateService HTTP/1.1\r\nAccept-Encoding: identity\r\nHost: 10.246.87.133\r\nX-Auth-Token: iwKUR3no1WOHB5PFkd5H\r\nAccept: */*\r\nConnection: close\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: OData-Version: 4.0
header: Allow: GET, PATCH, HEAD
header: ETag: "2BB654F0"
header: Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
header: X-Frame-Options: DENY
header: Pragma: no-cache
header: Cache-Control: no-Store,no-Cache
header: X-XSS-Protection: 1; mode=block
header: X-Content-Type-Options: nosniff
header: Content-Security-Policy: default-src 'none'; img-src 'self' data:; font-src 'self'; style-src 'self'; script-src 'self'; connect-src 'self' wss:; form-action 'none'; frame-ancestors 'none'; object-src 'none'; base-uri 'none'
header: Content-Type: application/json
header: Date: Tue, 05 Mar 2024 03:56:50 GMT
header: Connection: close
header: Content-Length: 1821
b'{\n  "@odata.id": "/redfish/v1/UpdateService",\n  "@odata.type": "#UpdateService.v1_8_0.UpdateService",\n  "Actions": {\n    "#UpdateService.SimpleUpdate": {\n      "TransferProtocol@Redfish.AllowableValues": [\n        [\n          "SCP"\n        ]\n      ],\n      "target": "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"\n    },\n    "Oem": {\n      "Nvidia": {\n        "#NvidiaUpdateService.CommitImage": {\n          "@Redfish.ActionInfo": "/redfish/v1/UpdateService/Oem/Nvidia/CommitImageActionInfo",\n          "target": "/redfish/v1/UpdateService/Actions/Oem/NvidiaUpdateService.CommitImage"\n        },\n        "#NvidiaUpdateService.PublicKeyExchange": {\n          "target": "/redfish/v1/UpdateService/Actions/Oem/NvidiaUpdateService.PublicKeyExchange"\n        },\n        "#NvidiaUpdateService.RevokeAllRemoteServerPublicKeys": {\n          "target": "/redfish/v1/UpdateService/Actions/Oem/NvidiaUpdateService.RevokeAllRemoteServerPublicKeys"\n        }\n      }\n    }\n  },\n  "Description": "Service for Software Update",\n  "FirmwareInventory": {\n    "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory"\n  },\n  "HttpPushUri": "/redfish/v1/UpdateService/update",\n  "HttpPushUriOptions": {\n    "HttpPushUriApplyTime": {\n      "ApplyTime": "OnReset"\n    }\n  },\n  "HttpPushUriTargets": [],\n  "Id": "UpdateService",\n  "MaxImageSizeBytes": 209715200,\n  "MultipartHttpPushUri": "/redfish/v1/UpdateService/update-multipart",\n  "MultipartHttpPushUri@Redfish.OperationApplyTimeSupport": {\n    "@odata.type": "#Settings.v1_3_3.OperationApplyTimeSupport",\n    "SupportedValues": [\n      "Immediate"\n    ]\n  },\n  "Name": "Update Service",\n  "ServiceEnabled": true,\n  "SoftwareInventory": {\n    "@odata.id": "/redfish/v1/UpdateService/SoftwareInventory"\n  },\n  "Status": {\n    "Conditions": [],\n    "State": "Enabled"\n  }\n}'
send: b'GET /redfish/v1/UpdateService HTTP/1.1\r\nAccept-Encoding: identity\r\nHost: 10.246.87.133\r\nX-Auth-Token: iwKUR3no1WOHB5PFkd5H\r\nAccept: */*\r\nConnection: close\r\n\r\n'
reply: 'HTTP/1.1 401 Unauthorized\r\n'
header: WWW-Authenticate: Basic
header: Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
header: X-Frame-Options: DENY
header: Pragma: no-cache
header: Cache-Control: no-Store,no-Cache
header: X-XSS-Protection: 1; mode=block
header: X-Content-Type-Options: nosniff
header: Content-Security-Policy: default-src 'none'; img-src 'self' data:; font-src 'self'; style-src 'self'; script-src 'self'; connect-src 'self' wss:; form-action 'none'; frame-ancestors 'none'; object-src 'none'; base-uri 'none'
header: Content-Type: application/json
header: Date: Tue, 05 Mar 2024 03:56:50 GMT
header: Connection: close
header: Content-Length: 829
glimchb commented 8 months ago

found it!!!

header: Connection: keep-alive

vs

header: Connection: close

reproduces with simple:

$ curl -k -H "X-Auth-Token: IDY0mdVOxAyNhS1XzBoL" -H "Connection: close" -X GET https://10.246.87.133/redfish/v1/UpdateService

also checked the code https://github.com/python/cpython/blob/3.9/Lib/urllib/request.py#L1331

jcastanos2 commented 8 months ago

OK, I think we have two issues: one is the unsecure_context I outlined before. Even when I run if with a valid token the first time, it fails with an exception. And, then, if I actually modify the community code to provide the unsecure_context in the urrlib_request.open, it works the first time and fails the second one (which is the close that Boris mentioned). I distilled the ansible redfish module to something simple (see the attached python script):

get2.py.txt

``sancho@doca-vr-ansible:~/opi/ansible-opi-dpu/jgctests$ echo $token
ltSvFgT3VYyIk93YbVvF
sancho@doca-vr-ansible:~/opi/ansible-opi-dpu/jgctests$ python3 ./get2.py ltSvFgT3VYyIk93YbVvF
{'msg': 'URL Error on GET request to '
        "'https://10.137.189.246/redfish/v1/UpdateService': '[SSL: "
        'CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed '
        "certificate (_ssl.c:1007)'",
 'ret': False}
sancho@doca-vr-ansible:~/opi/ansible-opi-dpu/jgctests$ python3 ./get2.py -U ltSvFgT3VYyIk93YbVvF
{'data': {'@odata.id': '/redfish/v1/UpdateService',
          '@odata.type': '#UpdateService.v1_8_0.UpdateService',
          'Actions': {'#UpdateService.SimpleUpdate': {'TransferProtocol@Redfish.AllowableValues': [['SCP']],
                                                      'target': '/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate'},
                      'Oem': {'Nvidia': {'#NvidiaUpdateService.CommitImage': {'@Redfish.ActionInfo': '/redfish/v1/UpdateService/Oem/Nvidia/CommitImageActionInfo',
                                                                              'target': '/redfish/v1/UpdateService/Actions/Oem/NvidiaUpdateService.CommitImage'},
                                         '#NvidiaUpdateService.PublicKeyExchange': {'target': '/redfish/v1/UpdateService/Actions/Oem/NvidiaUpdateService.PublicKeyExchange'},
                                         '#NvidiaUpdateService.RevokeAllRemoteServerPublicKeys': {'target': '/redfish/v1/UpdateService/Actions/Oem/NvidiaUpdateService.RevokeAllRemoteServerPublicKeys'}}}},
          'Description': 'Service for Software Update',
          'FirmwareInventory': {'@odata.id': '/redfish/v1/UpdateService/FirmwareInventory'},
          'HttpPushUri': '/redfish/v1/UpdateService/update',
          'HttpPushUriOptions': {'HttpPushUriApplyTime': {'ApplyTime': 'OnReset'}},
          'HttpPushUriTargets': [],
          'Id': 'UpdateService',
          'MaxImageSizeBytes': 209715200,
          'MultipartHttpPushUri': '/redfish/v1/UpdateService/update-multipart',
          'MultipartHttpPushUri@Redfish.OperationApplyTimeSupport': {'@odata.type': '#Settings.v1_3_3.OperationApplyTimeSupport',
                                                                     'SupportedValues': ['Immediate']},
          'Name': 'Update Service',
          'ServiceEnabled': True,
          'SoftwareInventory': {'@odata.id': '/redfish/v1/UpdateService/SoftwareInventory'},
          'Status': {'Conditions': [], 'State': 'Enabled'}},
 'headers': {'allow': 'GET, PATCH, HEAD',
             'cache-control': 'no-Store,no-Cache',
             'connection': 'close',
             'content-length': '1821',
             'content-security-policy': "default-src 'none'; img-src 'self' "
                                        "data:; font-src 'self'; style-src "
                                        "'self'; script-src 'self'; "
                                        "connect-src 'self' wss:; form-action "
                                        "'none'; frame-ancestors 'none'; "
                                        "object-src 'none'; base-uri 'none'",
             'content-type': 'application/json',
             'date': 'Tue, 05 Mar 2024 19:42:20 GMT',
             'etag': '"2BB654F0"',
             'odata-version': '4.0',
             'pragma': 'no-cache',
             'strict-transport-security': 'max-age=31536000; '
                                          'includeSubdomains; preload',
             'x-content-type-options': 'nosniff',
             'x-frame-options': 'DENY',
             'x-xss-protection': '1; mode=block'},
 'resp': <http.client.HTTPResponse object at 0x7fdbad5dcb20>,
 'ret': True}
sancho@doca-vr-ansible:~/opi/ansible-opi-dpu/jgctests$ python3 ./get2.py -U ltSvFgT3VYyIk93YbVvF
{'msg': 'HTTP Error 401 on GET request to '
        "'https://10.137.189.246/redfish/v1/UpdateService', extended message: "
        "'While accessing the resource at '/redfish/v1/UpdateService', the "
        "service received an authorization error 'Invalid username or "
        "password'.'",
 'ret': False,
 'status': 401}
glimchb commented 8 months ago

unsecure_context

I don't see an issue with unsecure_context in my environment. I will double-check again. for mee looks like validate_certs is always false, so context should already be unverified see https://github.com/ansible/ansible/blob/stable-2.16/lib/ansible/module_utils/urls.py#L1440