先说解决办法以及结果
解决办法:Nginx 转发
结果:只转发 生成vnc 的console 地址,拼凑出https,能在Https web应用即可,比如把console url (控制台地址)iframe 进自己的页面中。
说明:如果你也是这种方式,只用一个控制台地址,放在https的web里,截至目前为至(2022年7月20日),网上大部分配置Open Stack内部组件通信的Https SSL证书的方式,我都试过了,结论就是一个,自签名证书验证失败。
如果你有自己的正规机构颁发的证书,其实也不用像你百度出来的教程,官网文档也有 不过藏得比较深,
地址:官方文档
官方是 在给horizon 配置https的过程中 配置了nova 的https 以供 console的使用。
此文章可以转发,但必须标注出处和作者,否则追究其责任
前言
openstack中默认的各个组件之间通信都是用的http,包括horizon都是,这就对一些web开发上出现一些问题。我这边开发spring项目在外网,https 是再正常不过的事,所以就开始想办法给openstack配置上 SSL,但是在生产过程中,用到的签名都是自己生成的,这就埋下了隐患。
环境
Ubuntu 20.04.4 LTS openstack yoga版本 全是新的,
嘎嘎新的系统,
嘎嘎新的Open Stack版本,
(2022年7月20日),
唉,也就是这个原因网上的教程都不多,资源有限,让我这个初学者真头大
解决过程
方法 1
给open stack 配上ssl
方法 2
只给open stack nova配上ssl
方法 3
nginx 代理转发
方法1
先说说方法1,我安装的是all-in-one ,我就一台机子,最小配置, keystone,glance,nova,placement,neutron 还有cinder,后来cinder被我停了,暂时不用,这要是给各个组件配置太麻烦了,网上也有几个教程
https://blog.csdn.net/zhongbeida_xue/article/details/101017148 这个是19年的
https://blog.csdn.net/weixin_42159480/article/details/118364310 这个是21年的但是train版本,现在官网已经过去6个版本了。。
有关这方面的都挺老的,也有借鉴意义
但是这个太费功夫,我就只想要vnc console url 带上https就行
方法2
只给nova配置 也不是不行,毕竟只有一个组件,但是查了查资料还是我太天真,因为版本问题,好多的配置文件中的配置项都变了,最后我还是从官网查到的,就是上边我提到的地址:官方文档
在给horizon 配置https的过程中 配置了nova 的https 以供 console的使用。
所以我就尝试了一下,但是emmmmmmm
跟着官网走,我先是把horizon配成了https,暂时没有问题,至于我为啥要配置,纯粹是为了跟着文档走
接下来配置nova 配置完文档上的东西,重启服务后,查看日志出现了错误,
日志在/var/log/nova/nova-api.log
- 1、cert_file 说我在配置文件没有这个“cert_file ”配置项 但是官网给的配置项是 cert 和key
所以我就把新的加上了,也把以前老的配置项加上了 ssl_cert_file ssl_key_file - 2、文件名称不对
2022-07-18 16:29:48.030 17517 ERROR oslo_service.service RuntimeError: Unable to find cert_file : /root/ssl/signing_cert.pem
但我的确是配置了证书而且不是他提示的 “signing_cert.pem” 我配置的server.crt
然后我从网上查到过教程来说,改了两个问题:
- 更改证书 chown -R nova:nova “证书”,好让nova能用,
- 其次我在/etc/nova/文件夹下 创建了ssl文件夹赋予同样的用户组和用户,并拷贝了证书文件过去,这下重启后总算是不报错了。
但出现了如下问题:
问题
自签名证书问题
现在新版本的open stack用的都是python3 包括一些包,用的都是新的,所以出现的第一个问题就是自己签发的证书被python拦截下来。导致验证不通过。
ssl.SSLError:[SSL:CERTIFICATE_VERIFY_FAILED]
在这个过程中我的证书是crt 也有配置pem的 可能报错不一样但是结论是一样的 ,python这边好像放行不了这种证书,网上的解决办法一般是 在发送请求时候加上 verify=false
比如
requests.get('https://www.baidu.com', verify=False)
但是没用啊,我总不能去改open stack python源码吧,多了去,我咋改。何况我是干Java的小白
所以至此为止,如果你有真正的证书我估计应该已经可以了,但是如果是生产过程中用的自己生成的证书,那估计不行。
所以我用了我最不想用的方法 Nginx
方法3 Nginx
这个我作为java开发我是真的不会,工作时间短,我还没来得及接触(但这不接来了😒)
先安装吧,
我在另一台电脑,安装了nginx,ip是 192.168.27.58 (同样安装在open stack节点上也行,我试过了), 我就不多说了,centos 和 Ubuntu 也不太一样 , 还有down下来 make 现编译的,网上教程很多,反正我是小白,咱就是说,就是一个apt install nginx -y 爱咋的咋的,主要是配置
来看配置
先找一下nginx 在哪
配置文件在 etc/nginx/nginx.conf下
但是呢 ,我也不知道上来就按照网上的教程给他改了(虽然做了备份),结果就是,一堆错,格式不对,后来我才知道,原来网上那些直接配置server的都是这个文件的配置子文件
在上图中,这有个include 地址下 去新建符合格式的配置文件
我建了一个openstack-nginx.conf 文件
在做配置文件之前,如果你有心的话,应该去看看 那个控制台页面的结构,以方便理解写配置文件
vnc_auto.html
这个文件所在位置:/usr/share/novnc 全局查找一下就好
页面上,不用看引入的js,只看页面中js,
在< head > 中 位置如下
大致看一眼就能看来他想用websocket
往下找,
这个位置,是表明,这个页面会根据 https还是http来更换端口,
接着往下看
此处是重点,他在页面中拼接了一个 url地址供给rfb去调用后面websocket,同样根据是否是https链接,来选择是ws还是wss,也就是说 websocket的连接地址是在此处生成的。
红色框内的内容是我自己 修改源代码 加入进去的 ,为了方便查看
url += "/wss" + path;
其中这一句是用于nginx 转发websocket的ws://*** 用的
也就是说在我修改完源代码页面后,如果访问https 控制台地址,websoket的链接是:
wss:// * * * * */wss?token=* * * * * * *
这种格式
而其中的/wss是我自己写的,你们写成别的也可以 比如/websocket 啥都行,但是要在nginx配置中对应,这个主要是nginx监听websocket 以转发用的。
接下里看看具体nginx怎么配置的
map $http_upgrade $conn_upgrade {
default upgrade;
'' close;
}
upstream nova
{
server 192.168.27.48:6080;
}
server {
listen 80;
listen 443 ssl;
server_name 192.168.27.58;
ssl on;
ssl_certificate /etc/nginx/server.crt;
ssl_certificate_key /etc/nginx/server.key;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_redirect off;
proxy_pass http://nova;
}
location /wss {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto http; #这个是重点!!!!是个坑后面会讲(记得删掉#后面的注释)
proxy_redirect off;
proxy_pass http://nova;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connetion $conn_upgrade;
}
access_log off;
}
做一下说明:
首先我这个配置文件时最后成功时的最终配置,基本一股脑的抄就行,个别地方自己做修改,证书位置、ip 、名字啥的
首先nginx所在的服务器(电脑)host配置上 控制节点 我这里是192.168.27.48 controller
192.168.27.48:6080 : 192.168.27.48 是我open stack服务器的nova节点地址(和controller在一起),端口号6080是console vnc的接口 这个是指定的改不了,
192.168.27.58 : 这是我nginx 的电脑的地址,直接监听80端口 转发到443上,如果你nginx安装在 openstack 服务器上,不能监听80端口,因为此端口被horizon占用着,切记。
主要说明的是:
1、location /
这个是配置转发 console url 地址的
2、location /wss
这个是另一大问题,就是当console url https:////vnc_auto.htm?path=
这个页面时候,他的js 会把websocket 从ws改成wss(上面已经说过),也是这个时候我才知道,他起了一个websocket服务器 用来画这个命令行界面,(日哦)
所以 这就得把ws也得交给nginx转发成wss。
至于说为啥是 “/wss” 我也在上文中解释过。
如果一切顺利的话(怎么可能),我们接下来做一些验证操作
首先保证nginx 启动正常,配置文件没有不合法的问题
- 去访问下 https://192.168.27.58/vnc_auto.html(https://controller/vnc_auto.html) —这个58地址就是nginx那个服务器的地址,且监听的80端口,server_name 写的也是
- 如果出现了以下页面就代表这nginx转发 控制台页面成功了
因为我们没有给path 后面的token嘛,所以失败也正常
只是说 通过https的方式能访问的这个页面就可以了
下一步:
我们从命令中启动一个虚拟机 然后查看一下console url 看看真实的情况
root@controller:~# openstack server list
/usr/lib/python3/dist-packages/secretstorage/dhcrypto.py:15: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead
from cryptography.utils import int_from_bytes
/usr/lib/python3/dist-packages/secretstorage/util.py:19: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead
from cryptography.utils import int_from_bytes
+--------------------------------------+------------+---------+------------------------+---------+-------------+
| ID | Name | Status | Networks | Image | Flavor |
+--------------------------------------+------------+---------+------------------------+---------+-------------+
| ae5ee76f-3dc0-4de3-8243-26ee47751e46 | vm2 | SHUTOFF | selfservice=10.0.0.162 | cirror | m1.nano |
| e7f6f376-5a50-4031-bfb5-e8ade9cdb6d7 | vm1 | SHUTOFF | selfservice=10.0.0.223 | cirror | m1.nano |
| afa53d9e-ed0c-423f-9df4-65012276ea30 | cirros4j 2 | SHUTOFF | networke4j=10.0.0.221 | ubuntu1 | ubuntu tiny |
+--------------------------------------+------------+---------+------------------------+---------+-------------+
启动vm1
root@controller:~# openstack server start vm1
ok 我选择启动了vm1
查看vm1的vnc url
root@controller:~# openstack console url show vm1
/usr/lib/python3/dist-packages/secretstorage/dhcrypto.py:15: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead
from cryptography.utils import int_from_bytes
/usr/lib/python3/dist-packages/secretstorage/util.py:19: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead
from cryptography.utils import int_from_bytes
+----------+-------------------------------------------------------------------------------------------+
| Field | Value |
+----------+-------------------------------------------------------------------------------------------+
| protocol | vnc |
| type | novnc |
| url | http://controller:6080/vnc_auto.html?path=%3Ftoken%3D89fa5c04-a7bd-4ec5-980a-afb24cd75e5e |
+----------+-------------------------------------------------------------------------------------------+
此时我们发现 url地址还是http 的形式,因为毕竟,在open stack服务器上 各个组件我们都没有动过配置。
我们主要得到的是token 就是path后面的。
然后我们自己拼接上去 第一步测试的地址后面
https://192.168.27.58/vnc_auto.html?path=%3Ftoken%3D89fa5c04-a7bd-4ec5-980a-afb24cd75e5e
可以在控制台中看到 websoket 改造完的地址是 wss://192.168.27.58:443/wss?token=89fa5c04-a7bd-4ec5-980a-afb24cd75e5e 其中/wss被nginx监听转发到内部/ws
至此应该就成功了。
接下来说一些一些疑点问题
疑点-问题-错误
有可能 在上一步 获取真实的token,拼接完整的url地址 访问依然报错(比如我就是)
控制台说,依然连接不上,
此时我去查了一下日志nova 的novnc日志
截取部分如下
2022-07-19 17:20:24.267 43633 INFO nova.console.websocketproxy [-] 127.0.0.1 - - [19/Jul/2022 17:20:24] 127.0.0.1: Plain non-SSL (ws://) WebSocket connection
2022-07-19 17:20:24.269 43633 INFO nova.console.websocketproxy [-] 127.0.0.1 - - [19/Jul/2022 17:20:24] 127.0.0.1: Path: '/wss?token=2a4f2cf4-b49f-4ecd-a188-83224911b067'
2022-07-19 17:20:26.041 43633 INFO nova.console.websocketproxy [req-dcb5e7ba-0a27-42c1-bff2-20f2c326e5d0 - - - - -] handler exception: Origin header protocol does not match this host.
第一条说是准备一个 no-SSL的 websoket连接
第二条说是获得了path,
第三条是重点,说是 Origin header protocol does not match this host.
意思是:原始报头协议与此主机不匹配。
这就很迷了,我在nginx 都转发了真是的地址啊,等等!!真实地址?
此时我决定搜一搜 ’Origin header protocol does not match this host. ‘
一下找到了源码,发现这个报错的python文件叫做 websocketproxy.py
那我就找一下
root@controller:~# find / -name websocketproxy.py
/usr/lib/python3/dist-packages/websockify/websocketproxy.py
/usr/lib/python3/dist-packages/nova/console/websocketproxy.py
find: ‘/run/user/1000/doc’: 权限不够
find: ‘/run/user/1000/gvfs’: 权限不够
根据网上的提示,真正的文件是第二个 /usr/lib/python3/dist-packages/nova/console/websocketproxy.py
那我就进去看看
通过vim的查找,找到这句话
# Verify Origin 来验证下源
expected_origin_hostname = self.headers.get('Host')
if ':' in expected_origin_hostname:
e = expected_origin_hostname
if '[' in e and ']' in e:
expected_origin_hostname = e.split(']')[0][1:]
else:
expected_origin_hostname = e.split(':')[0]
expected_origin_hostnames = CONF.console.allowed_origins
expected_origin_hostnames.append(expected_origin_hostname)
origin_url = self.headers.get('Origin')
# missing origin header indicates non-browser client which is OK
# 即使没有这个也是ok的 可能是用于IOS 浏览器吧
if origin_url is not None:
origin = urlparse.urlparse(origin_url)
origin_hostname = origin.hostname
origin_scheme = origin.scheme
# If the console connection was forwarded by a proxy (example:
# haproxy), the original protocol could be contained in the
# X-Forwarded-Proto header instead of the Origin header. Prefer the
# forwarded protocol if it is present.
#问题在这里 如果经过了代理服务器的转发 他回去拿X-Forwarded-Proto 当作此时的origin_scheme 由于我们转发了真实的协议
forwarded_proto = self.headers.get('X-Forwarded-Proto')
if forwarded_proto is not None:
origin_scheme = forwarded_proto
if origin_hostname == '' or origin_scheme == '':
detail = _("Origin header not valid.")
raise exception.ValidationError(detail=detail)
if origin_hostname not in expected_origin_hostnames:
detail = _("Origin header does not match this host.")
raise exception.ValidationError(detail=detail)
if not self.verify_origin_proto(connect_info, origin_scheme):
# 报错位置在这里 因为此if判断不通过
detail = _("Origin header protocol does not match this host.")
raise exception.ValidationError(detail=detail)
我们可以看到 在最后一个if 判断不通过 导致的报错 “Origin header protocol does not match this host.” 我能看懂python 但我又不会,他到底是怎么判断的,所以最笨的方法
改一下源码输出点 判断条件看看(个人认为改源码是大忌,但是我只看 又不改他逻辑,所以问题不大)
if not self.verify_origin_proto(connect_info, origin_scheme):
detail = _("Origin header protocol does not match this host."+"connect_info:"+str(connect_info)+",origin_scheme:"+str(origin_scheme))
raise exception.ValidationError(detail=detail)
妥妥,重启下nova服务,重新走一遍再看输出
handler exception: Origin header protocol does not match this host.connect_info:ConsoleAuthToken(access_url_base=‘http://controller:6080/vnc_auto.html’,console_type=‘novnc’,created_at=2022-07-19T09:37:53Z,host=‘192.168.27.48’,id=96,instance_uuid=e7f6f376-5a50-4031-bfb5-e8ade9cdb6d7,internal_access_path=None,port=5900,token=‘6e34ceac-1d36-45a2-9c46-4fe74df6799b’,updated_at=None),origin_scheme:https
此时看到两个判断条件,我就看出来了, origin_scheme是https 由于nginx 之前我配置的是 (跟网上学的)
proxy_set_header X-Forwarded-Proto $scheme
转发真实的协议 但是我们nginx是做的 wss-》ws 内部用的还是 http connect_info下没一个https 字眼,所以再怎么傻我也知道,判断肯定是不通过,所以我就投机取巧
nginx配置改为
proxy_set_header X-Forwarded-Proto http
跳过你的判断不就行了
重启nginx
重新走openstack 获取vm1 控制台地址流程,浏览器拼接,访问,成功!!!!
后续
我的程序中用的是openstack4j api 在调整好一切后 拿到特定虚拟机的控制台地址
依然是http://controller:6080/vnc_auto.html?path=%3Ftoken%3D89fa5c04-a7bd-4ec5-980a-afb24cd75e5e
我只取path后面的字符串发给前端,前端在前面拼接上https://192.168.27.58/vnc_auto.html 然后放到iframe中去 ,剩下的基本就是前端同学的活了,来调用我接口就好。