Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help with SNI #734

Open
lakshmisivareddy opened this issue Jul 2, 2023 · 2 comments
Open

Help with SNI #734

lakshmisivareddy opened this issue Jul 2, 2023 · 2 comments
Labels
Status: Available No one has claimed responsibility for resolving this issue. Status: More info needed More information needed from issue author

Comments

@lakshmisivareddy
Copy link

lakshmisivareddy commented Jul 2, 2023

Hi Team ,
i have a Multiple MQTT Broker hosted in K8's , these MQTT Brokers are behind the ingress controller
ingress controller routes the traffic to appropriate broker based on SNI
for non TLS i am able to verify the connection using below command
openssl s_client -showcerts -connect istio-test.westus2.cloudapp.azure.com:8883 -servername example1.test.com
openssl s_client -showcerts -connect istio-test.westus2.cloudapp.azure.com:8883 -servername example2.test.com

with TLS traffic i am not able to set specific SNI (servername).
By default SNI going as istio-test.westus2.cloudapp.azure.com

please fine the sample i am trying

import paho.mqtt.client as paho
from paho.mqtt import client as mqtt
import ssl
import time
import socket
#path_to_root_cert = "/home/challal/Downloads/cacert.pem"
#path_to_root_cert = "/home/challal/Downloads/certs/azure-iot-test-only.root.ca.cert.pem"
path_to_root_cert = "/home/challal/Downloads/test.pem"
device_id = "pub_cert"
cert_file = "/Users/l0c0gvk/Workspace/Testing/mqtttest/example_certs/device_cert_filename.pem"
key_file = "/Users/l0c0gvk/Workspace/Testing/mqtttest/example_certs/device_cert_key_filename.key"
ca_cert='/Users/l0c0gvk/Workspace/Testing/mqtttest/example_certs/root_CA_cert_filename.pem'

def on_connect(client, userdata, flags, rc):
    print("Device connected with result code: " + str(rc))

def on_disconnect(client, userdata, rc):
    print("Device disconnected with result code: " + str(rc))

def on_publish(client, userdata, mid):
    print("Device sent message")

def sni_callback(sock, req_hostname, cb_context, as_callback=True):
     print('sni_callback')
    #  context1 = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
    #  context1.load_cert_chain(certfile=cert_file,keyfile=key_file)
    #  context1.wrap_socket(socket.socket(socket.AF_INET),server_hostname="example1.test.com")
     print('Loading certs for {}'.format(req_hostname))
    # print(type(cb_context))



client =  paho.Client(client_id=device_id,clean_session=True,userdata=None,protocol=mqtt.MQTTv311)  

client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_publish = on_publish

# Set the certificate and key paths on your client

#client.tls_set(ca_certs=path_to_root_cert, certfile=cert_file, keyfile=key_file,
#               cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)

ssl_ctx = ssl.create_default_context(cafile=ca_cert)
ssl_ctx.check_hostname = False

# ssl_ctx.load_cert_chain(certfile=cert_file, keyfile=key_file)
# ssl_ctx.verify_mode = ssl.CERT_NONE
# client.tls_set_context(ssl_ctx)
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)


context.load_verify_locations(ca_cert)
context.load_cert_chain(cert_file, key_file)

#context.wrap_socket(server_hostname="example1.test.com")

context.sni_callback=sni_callback

client.tls_set_context(context)
client.tls_insecure_set(True)


client.connect("istio-test.westus2.cloudapp.azure.com", 8883,60)
print(type(client.socket))
client.loop_start()
while True:
   client.publish("test_topic", "{id=123}", qos=1)
   time.sleep(0.1)

Can some one help me here

@github-actions github-actions bot added the Status: Available No one has claimed responsibility for resolving this issue. label Jul 2, 2023
@MattBrittan
Copy link
Contributor

I believe you wish to change server_hostname within wrap_socket; unfortunately this is currently fixed in the code (reconnect function):

                # Try with server_hostname, even it's not supported in certain scenarios
                sock = self._ssl_context.wrap_socket(
                    sock,
                    server_hostname=self._host,
                    do_handshake_on_connect=False,
                )

One option would be to do this via DNS (e.g. a CNAME for example1.test.com, using a domain you own!, pointing to istio-test.westus2.cloudapp.azure.com); that should work as-is.

Alternatively see the subclass example. Using this technique you can override reconnect() and configure the server_hostname as you require.

As this would seem to be a fairly rare requirement I'm going to leave it there; please let us know if that is useful or you believe modifications to the library are needed (given this was logged sometime ago I'd guess you may already have a solution).

@MattBrittan MattBrittan added the Status: More info needed More information needed from issue author label Jan 8, 2024
@bram-tv
Copy link

bram-tv commented Jul 29, 2024

As this would seem to be a fairly rare requirement I'm going to leave it there; please let us know if that is useful or you believe modifications to the library are needed (given this was logged sometime ago I'd guess you may already have a solution).

Just adding my 2 cents to this issue: I was looking for the same option (specify the server_hostname of the SSL context): our MQTT broker has several servers all of which are behind a single record (i.e. DNS returns multiple A records for the same name) and we needed to verify that all IPs are working as expected [in our case it was mainly to verify the firewall in front of our clients].

The work-around we applied:

import paho.mqtt.client as mqtt
import ssl
import socket as _socket

ip = "127.2.3.4"
port = 8883
host = "foo.example.com"
client_id = "foo"

class ServerNameClient(mqtt.Client):
    def _ssl_wrap_socket(self, tcp_sock: _socket.socket) -> ssl.SSLSocket:
        orig_host = self._host
        self._host = host
        res = super()._ssl_wrap_socket(tcp_sock)
        self._host = orig_host
        return res

mc = ServerNameClient(mqtt.CallbackAPIVersion.VERSION2, client_id=client_id)
...
mc.connect(ip, port=port)
mc.loop_forever()

In other threads regarding SNI (i.e. #133 (comment)) there was some fear that adding it may confuse users but that might be avoidable if the server_hostanme was an option of Client.tls_set, i.e. that one could do something like:

mc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=client_id)
mc.tls_set(server_hostname="foo.example.com")
mc.connect(ip, port=port)
mc.loop_forever()

and that _ssl_wrap_socket would then prefer it (foo.example.com) over the host param of connect

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Available No one has claimed responsibility for resolving this issue. Status: More info needed More information needed from issue author
Projects
None yet
Development

No branches or pull requests

3 participants