基于TLS加密的虚拟机事件发布[代码实现]


1. 前言

网络技术不断发展,网络中存在的攻击行为也层出不穷,恶意攻击者作为中间人窃取网络信息,篡改网络信息,或者冒充合法服务器的行为,也成为我们设计网络协议,搭建网络通信服务器时,必须重点考虑的问题。
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的轻量级协议,该协议构建于TCP/IP协议之上,MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。
该协议默认情况下,以密文的形式进行消息的发布。因此,在本章节中,将通过TLS协议对MQTT发布的信息进行加密,确保消息的安全传输,保证数据的保密性和数据保证性。
最终效果是,虚拟机发生状态变化时,发布者会以事件的形式,通知到订阅者,发布的事件信息通过TLS协议进行加密。

2. MQTT协议的服务端和客户端如何获取证书?

2.1 服务端的配置

服务端,即broker,代理服务器。在broker上的配置非常重要。
在broker上生成好,CA证书,CA密钥,broker证书,broker密钥,client证书和client密钥。后续client,即发布者和订阅者上,只需要直接拷贝broker上生成好的CA证书,CA密钥,client证书和client密钥即可。

2.1.1生成CA证书和CA密钥

创建目录certs,在certs目录下创建目录ca,在ca目录下,使用如下命令:
openssl req -new -x509 -days 365 -extensions v3_ca -keyout ca.key -out ca.crt
在这里插入图片描述
这个地方的Common Name对于后续影响不大,设置为openssl,之类的都可以。

2.1.2生成broker密钥(即,broker.key)

在certs目录下创建目录broker,在broker目录下,使用如下命令:
openssl genrsa -out broker.key 2048

2.1.3 使用broker密钥,创建一个签名请求文件(即,broker.csr)

在broker目录下,使用如下命令:
openssl req -out broker.csr -key broker.key –new
在这里插入图片描述
注意:这里的Common Name非常重要!!必须填域名!!
broker上面填主机名就可以了,例如:61服务器的主机名为libing-dev-61,那么这里就填入libing-dev-61。

2.1.4将证书签名请求(csr)文件传递给我们的验证机构

在broker目录下,使用如下命令:
openssl x509 -req -in broker.csr -CA …/ca/ca.crt -CAkey …/ca/ca.key -CAcreateserial -out broker.crt -days 100
在这里插入图片描述

2.1.5生成client密钥(即,client.key)

在certs目录下创建目录client,在client目录下,使用如下命令:
openssl genrsa -out client.key 2048

2.1.6根据client密钥,创建client签名请求文件(即,client.csr)

在client目录下,使用如下命令:
openssl req -out client.csr -key client.key –new
在这里插入图片描述

这里的Common Name非常重要!!必须填域名!!
broker上面填主机名就可以了,例如:61服务器的主机名为libing-dev-61,那么这里就填入libing-dev-61。

2.1.7将证书签名请求(csr)文件传递给我们的验证机构

在client目录下,使用如下命令:
openssl x509 -req -in client.csr -CA …/ca/ca.crt -CAkey …/ca/ca.key -CAcreateserial -out client.crt -days 100
到了这里单机模式下的MQTT+TLS就完成了,可以先测试一下单机模式能不能通。
修改配置文件:
vim /etc/mosquitto/mosquitto.conf
在这里插入图片描述

重启mosquitt服务:
systemctl restart mosquitto.service
第一个窗口:
mosquitto_sub -p 18883 --cafile …/ca/ca.crt --cert client.crt --key client.key -h libing-dev-61 -t ab -d
第二个窗口:
mosquitto_pub -p 18883 --cafile …/ca/ca.crt --cert client.crt --key client.key -h libing-dev-61 -m hello -t ab –d
其中,-d表示调试,debug,会看到明显的发布和订阅信息

2.2 发布者上的配置

a. 使用scp将CA证书,CA密钥,client证书和client密钥拷贝到发布者服务器上。
b. 配置mosquitto.conf配置文件,
c. 重启mosquitto服务。
命令如下:
scp root@broker的IP地址:/root/cLan/mosquitto/certs/ca/ca.crt ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/ca/ca.key ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/client/client.crt ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/client/client.key ./
vim /etc/mosquitto/mosquitto.conf
systemctl restart mosquitto.service
d. 绑定ip地址和域名
注意:这一步非常重要!!因为MQTT+TLS访问的时候,是通过域名解析为IP地址,然后再根据IP地址去访问的!!
命令如下:
vim /etc/hosts
broker的IP地址 broker的主机名
在/etc/hosts文件中,加入一条ip地址到域名的映射,即broker的IP地址 broker的主机名。

2.3订阅者上的配置

订阅者的配置和发布者的配置是一样的。
a. 使用scp将CA证书,CA密钥,client证书和client密钥拷贝到发布者服务器上。
b. 配置mosquitto.conf配置文件,
c. 重启mosquitto服务。
命令如下:
scp root@broker的IP地址:/root/cLan/mosquitto/certs/ca/ca.crt ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/ca/ca.key ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/client/client.crt ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/client/client.key ./
vim /etc/mosquitto/mosquitto.conf
systemctl restart mosquitto.service
d. 绑定ip地址和域名
注意:这一步非常重要!!因为MQTT+TLS访问的时候,是通过域名解析为IP地址,然后再根据IP地址去访问的!!
命令如下:
vim /etc/hosts
broker的IP地址 broker的主机名
在/etc/hosts文件中,加入一条ip地址到域名的映射,即broker的IP地址 broker的主机名。

2.4 实验验证

通过命令的方式进行验证。
60作为发布者:
mosquitto_pub -p 18883 --cafile …/ca/ca.crt --cert client.crt --key client.key -h libing-dev-61 -m hello -t ab –d

50作为订阅者:
mosquitto_sub -p 18883 --cafile …/ca/ca.crt --cert client.crt --key client.key -h libing-dev-61 -t ab -d
发布者:
在这里插入图片描述

订阅者:
在这里插入图片描述

3. 代码实现

3.1 发布者

#include <stdio.h>
#include <stdlib.h>
#include <libvirt/libvirt.h>
#include <pthread.h>
#include <time.h>
#include <string.h>
#include "mosquitto.h"

#define KEEP_ALIVE 60
#define MSG_MAX_SIZE  512

static int  myEventCallback(virConnectPtr conn,
		      virDomainPtr dom,
		      int event,
		      int detail,
		      void *opaque) 
{
    struct mosquitto *mosq = (struct mosquitto *)opaque;

    const char *name = virDomainGetName(dom);
    struct tm *currTime;
    time_t now;
    time(&now);
    currTime = localtime(&now);

    char buff[MSG_MAX_SIZE];

    snprintf(buff, MSG_MAX_SIZE, "%d/%d/%d %d:%d:%d event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
   
    mosquitto_publish(mosq, NULL, "topic1", strlen(buff) + 1, buff, 0, 0);

    return 0;
}

static void* eventThreadLoop() {
    while(1) {
        if(virEventRunDefaultImpl() < 0) {
            printf("Run errer.\n");
        }
    }
    abort();
}

int main(int argc, char *argv[]) {

    if(argc != 3) {
        fprintf(stderr, "please input:./keepPub ip port.\n");
        return -1;
    }

    const char *host = argv[1];
    int port = atoi(argv[2]);

    virConnectPtr conn = NULL;

    int eventid = VIR_DOMAIN_EVENT_ID_LIFECYCLE;

    virEventRegisterDefaultImpl();

    conn = virConnectOpen(NULL); 
    if(conn == NULL) {
        fprintf(stderr, "connect hypervisor is failed.\n");
        return -1;
    }

    pthread_t eventThread;

    pthread_create(&eventThread, NULL, eventThreadLoop, NULL);

    int ret;
    struct mosquitto *mosq;

    ret = mosquitto_lib_init();
    if(ret) {
        fprintf(stderr, "mosquitto lib init is failed.\n");
        return -1;
    }

    mosq = mosquitto_new("Pub", true, NULL);
    if(mosq == NULL) {
        fprintf(stderr, "create mosquitto instance is failed.\n");
        return -1;
    }


    const char *cafile = "/root/cLan/certs/ca/ca.crt";
    const char *capath = "/root/cLan/certs/ca";
    const char *certfile = "/root/cLan/certs/client/client.crt";
    const char *keyfile = "/root/cLan/certs/client/client.key";
    ret = mosquitto_tls_set(mosq, cafile, capath, certfile, keyfile, NULL);
    if(ret) {
        fprintf(stderr, "TLS certification  is failed.\n");
        return -1;
    }

    ret = mosquitto_connect(mosq, host, port, KEEP_ALIVE);
    if(ret) {
        fprintf(stderr, "connect broker is failed.\n");
        return -1;
    }

    ret = mosquitto_loop_start(mosq);
    if(ret) {
        fprintf(stderr, "mosquitto loop is failed.\n");
        return -1;
    }

    int id = virConnectDomainEventRegisterAny(conn, 
    				 	      NULL,
					      eventid,
					      VIR_DOMAIN_EVENT_CALLBACK(myEventCallback),
					      mosq,
					      NULL);

    while(1) pause();

    mosquitto_disconnect(mosq);

    mosquitto_destroy(mosq);

    mosquitto_lib_cleanup();

    virConnectDomainEventDeregisterAny(conn, id);

    virConnectClose(conn);

    return 0;
}

3.2 订阅者

#include <stdio.h>
#include <stdlib.h>
#include "mosquitto.h"

#define HOST "broker的主机名"
#define PORT 18883
#define KEEPALIVE 60

int running = 1;

void my_connect_callback(struct mosquitto *mosq, void *obj, int rec) {
    printf("Call the function: my_connect_callback.\n");
    if(rec){
        printf("On_connect error.\n");
        exit(1);
    } else {
	if(mosquitto_subscribe(mosq, NULL, "topic1", 0)) {
            printf("Set topic error.\n");
	    exit(1);
        }
    }
}

void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rec) {
    printf("Call the function: my_disconnect_callback.\n");
    running = 0;
}

void my_subscribe_callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int*granted_qos) {
    printf("Call the function: my_subscribe_callback.\n");
}

void my_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) {
    printf("Call the function: my_message_callback.\n");
    
    printf("Receive a message %s: %s", (char*)msg->topic, (char*)msg->payload);

    if(0 == strcmp((char*)msg->payload, "quit")) {
        mosquitto_disconnect(mosq);
    }
}

int main() {
    int ret = 0;
    struct mosquitto *mosq;

    ret = mosquitto_lib_init();
    if(ret) {
        printf("init lib is failed.\n");
        return -1;
    }

    mosq = mosquitto_new("sub", true, NULL);
    if(mosq == NULL) {
        printf("create new mosquitto instance failed.\n");
        mosquitto_lib_cleanup();
        return -1;
}

    mosquitto_connect_callback_set(mosq, my_connect_callback);
    mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);
    mosquitto_subscribe_callback_set(mosq, my_subscribe_callback);
    mosquitto_message_callback_set(mosq, my_message_callback);


    const char *cafile = "/root/cLan/mosquitto/certs/ca/ca.crt";
    const char *capath = "/root/cLan/mosquitto/certs/ca";
    const char *certfile = "/root/cLan/mosquitto/certs/client/client.crt";
    const char *keyfile = "/root/cLan/mosquitto/certs/client/client.key";
    ret = mosquitto_tls_set(mosq, cafile, capath, certfile, keyfile, NULL);
    if(ret) {
        printf("TLS certification is failed.\n");
        mosquitto_destroy(mosq);
        mosquitto_lib_cleanup();
        return -1;       
    }


    ret = mosquitto_connect(mosq, HOST, PORT, KEEPALIVE);
    if(ret) {
        printf("connect to broker is failed.\n");
        mosquitto_destroy(mosq);
        mosquitto_lib_cleanup();
        return -1;       
    }

    printf("Subscribe begin.\n");
    int loop = mosquitto_loop_start(mosq);
    if(loop) {
        printf("mosquitto loop error.\n");
        return -1;
    }

    const char cmd[10];
    while(running) {
        scanf("%s", cmd);
        if(0 == strcmp(cmd, "quit")) {
            running = 0;
        }
    }

    mosquitto_disconnect(mosq);    
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();
    printf("Subscribe end.\n");
    return 0;   
}


3.3 实验效果

在broker上使用tcpdump进行抓包,可以获取发布者发送的数据,通过wireshark打开,可以看到信息都通过TLS协议进行加密。
在这里插入图片描述

在这里插入图片描述

参考文献

[1] https://openest.io/en/services/mqtts-how-to-use-mqtt-with-tls/
[2] https://blog.csdn.net/Jacobsea/article/details/125616913
[3] https://blog.csdn.net/Jacobsea/article/details/125613457

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值