OpenBMC 的 webui 项目有两个,webui-vue 是 phosphor-webui 的替代项目,其中 phosphor-webui 项目当前已归档。
The webui-vue repository will replace phosphor-webui once it is deprecated. Webui-vue uses the Vue.js framework to interact with the BMC via the Redfish API.
OpenBMC Web User Interface Development
https://github.com/openbmc/docs/blob/master/development/web-ui.md
auth
bmcweb 支持 basic auth(当然也支持多种其它类型的认证):
❯ curl -k -X GET https://192.168.1.60:2443/redfish/v1/Managers/bmc -H 'Authorization: Basic cm9vdDowcGVuQm1j'
其中 cm9vdDowcGVuQm1j 是 root:0penBmc
的 base64 编码。
AuthX
https://github.com/openbmc/bmcweb#authx
所有 bmc 认证相关的信息都记录在 /home/root/bmcweb_persistent_data.json 文件中。
systemd service
bmcweb 进程由 systemd 进行管理:
$ systemctl cat bmcweb
# /usr/lib/systemd/system/bmcweb.service
[Unit]
Description=Start bmcweb server
Wants=network.target
After=network.target
[Service]
ExecReload=kill -s HUP $MAINPID
ExecStart=/usr/bin/bmcweb
Type=simple
WorkingDirectory=/home/root
[Install]
WantedBy=network.target
static assets
webui 静态资源在如下位置:
$ ls /usr/share/www/
DMTF_Redfish_logo_2017.svg favicon.ico.gz img js redfish.css
css google index.html.gz redfish styles
其中 css
, img
, js
, favicon.ico.gz
, index.html.gz
是通过 webui-vue 工程构建出来的。
tls
bmcweb 会自动生成默认的 HTTPS 证书,证书在如下位置:
$ cat /etc/ssl/certs/https/server.pem
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDADUHokzKpNvElYp1JN
X7R0txw0l3jQlOcAA5JhtGrfQXCNlAUrQ+mCv5TLLwUT90mhZANiAATi1SngpRxh
wo1lISVDDE78W6NFpLePX3SczBpq3NtpHFr2P7syuMh8JdZ4tnZ/pPDm/8kE+W7r
3dI6jcI/g/Kztdvyg1yNXqxLU/H2LhhEWDSITPUgnKjBSqElVIPJWrc=
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIICNzCCAb6gAwIBAgIEPR6/LDAKBggqhkjOPQQDAjAyMQswCQYDVQQGEwJVUzEQ
MA4GA1UECgwHT3BlbkJNQzERMA8GA1UEAwwIdGVzdGhvc3QwHhcNMjMxMTAzMDcw
MDM2WhcNMzMxMDMxMDcwMDM2WjAyMQswCQYDVQQGEwJVUzEQMA4GA1UECgwHT3Bl
bkJNQzERMA8GA1UEAwwIdGVzdGhvc3QwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATi
1SngpRxhwo1lISVDDE78W6NFpLePX3SczBpq3NtpHFr2P7syuMh8JdZ4tnZ/pPDm
/8kE+W7r3dI6jcI/g/Kztdvyg1yNXqxLU/H2LhhEWDSITPUgnKjBSqElVIPJWrej
gaQwgaEwDwYDVR0TAQH/BAUwAwEB/zATBgNVHREEDDAKggh0ZXN0aG9zdDAdBgNV
HQ4EFgQU6tqfW5Q+z2KegqzKINu3M08KHFcwCQYDVR0jBAIwADALBgNVHQ8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwLQYJYIZIAYb4QgENBCAWHkdlbmVyYXRl
ZCBmcm9tIE9wZW5CTUMgc2VydmljZTAKBggqhkjOPQQDAgNnADBkAjAvQjNK6rTd
Unq3RlSfoR0DPswa8Kk4gT7ui4oevvBefuIg+KOU4G1e479gcKOeqcACMDBtGtW7
WMhIGYnalbEsBJYFfRtLRt2IWqTTAHQrWwwn7Y876nzGBVTXBDcB8OzWgg==
-----END CERTIFICATE-----
因此如有需要,可以手工合并适用于 nginx tls 支持的私钥和证书为一个文件并进行替换。
build
❯ devtool modify webui-vue
❯ devtool build webui-vue
构建时可以设置 http_proxy
和 https_proxy
环境变量,npm install 时会使用这两个环境变量:
// meta-phosphor/recipes-phosphor/webui/webui-vue_git.bb
do_compile[network] = "1"
do_compile () {
cd ${S}
rm -rf node_modules
npm --loglevel info --proxy=${http_proxy} --https-proxy=${https_proxy} install
npm run build ${EXTRA_OENPM}
}
do_install () {
# create directory structure
install -d ${D}${datadir}/www
cp -r ${S}/dist/** ${D}${datadir}/www
find ${D}${datadir}/www -type f -exec chmod a=r,u+w '{}' +
find ${D}${datadir}/www -type d -exec chmod a=rx,u+w '{}' +
}
构建出来的内容见如下目录(还有其它一些目录,可以通过 oe-workdir/temp
目录下的日志文件进行了解):
❯ ls build/romulus/workspace/sources/webui-vue/oe-workdir/image/usr/share/www
css favicon.ico.gz img index.html.gz js
❯ ls build/romulus/workspace/sources/webui-vue/oe-workdir/package/usr/share/www
css favicon.ico.gz img index.html.gz js
debug logging
日志输出默认为 disabled 级别,因此所有日志打印都无法输出。
option(
'bmcweb-logging',
type: 'combo',
choices : [ 'disabled', 'enabled', 'debug', 'info', 'warning', 'error', 'critical' ],
value: 'disabled',
description: '''Enable output the extended logging level.
- disabled: disable bmcweb log traces.
- enabled: treated as 'debug'
- For the other logging level option, see DEVELOPING.md.'''
)
❯ vi build/romulus/workspace/appends/bmcweb_git.bbappend
EXTRA_OEMESON:append = " -Dbmcweb-logging=debug"
bmcweb 的日志可以通过 journactl 进行查看:
$ sudo journalctl -f -u bmcweb
Dec 02 14:20:23 romulus bmcweb[518]: [INFO webserver_main.cpp:46] Starting webserver on socket handle 3
Dec 02 14:20:23 romulus bmcweb[518]: [INFO webserver_main.cpp:142] Start Hostname Monitor Service...
websocket
/subscribe
websocket 由 dbus_monitor.hpp 注册,需要使能 rest
特性:
option(
'rest',
type: 'feature',
value: 'disabled',
description: '''Enable Phosphor REST (D-Bus) APIs. Paths directly map
Phosphor D-Bus object paths, for example,
/xyz/openbmc_project/logging/entry/enumerate. See
https://github.com/openbmc/docs/blob/master/rest-api.md.'''
)
❯ vi build/romulus/workspace/appends/bmcweb_git.bbappend
EXTRA_OEMESON:append = " -Drest=enabled"
需要注意的是,整个 webui 有多处 websocket 请求,包括:/subscribe
, /kvm
, /console
, /vm
(/vm
实际上是 WebSocketEndpoint
,即服务端动态生成的路径),搜索 wss
前缀即可。
nginx reverse proxy
需求
nginx 使用 HTTP 对外提供服务,通过 nginx 反向代理 bmcweb,且静态资源仍然由 bmcweb 后端提供服务。
bmcweb 后端和 webui-vue 前端都需要进行修改,相应改动如下。
bmcweb 后端
增加如下构建选项:
❯ vi build/romulus/workspace/appends/bmcweb_git.bbappend
EXTRA_OEMESON:append = " -Dinsecure-disable-xss=enabled"
增加该构建选项是为了支持 OpenBMC 页面通过 iframe 嵌入。
修改如下代码:
diff --git a/include/login_routes.hpp b/include/login_routes.hpp
index ae99757e..ffc793de 100644
--- a/include/login_routes.hpp
+++ b/include/login_routes.hpp
@@ -200,8 +200,8 @@ inline void handleLogout(const crow::Request& req,
"SESSION="
"; SameSite=Strict; Secure; HttpOnly; "
"expires=Thu, 01 Jan 1970 00:00:00 GMT");
- asyncResp->res.addHeader("Clear-Site-Data",
- R"("cache","cookies","storage")");
+ // asyncResp->res.addHeader("Clear-Site-Data",
+ // R"("cache","cookies","storage")");
persistent_data::SessionStore::getInstance().removeSession(session);
}
}
diff --git a/include/security_headers.hpp b/include/security_headers.hpp
index 1b9e984d..7cf1dbbc 100644
--- a/include/security_headers.hpp
+++ b/include/security_headers.hpp
@@ -60,7 +60,7 @@ inline void addSecurityHeaders(const crow::Request& req [[maybe_unused]],
res.addHeader("X-Permitted-Cross-Domain-Policies", "none");
res.addHeader("Cross-Origin-Embedder-Policy", "require-corp");
- res.addHeader("Cross-Origin-Opener-Policy", "same-origin");
+ res.addHeader("Cross-Origin-Opener-Policy", "unsafe-none");
res.addHeader("Cross-Origin-Resource-Policy", "same-origin");
if (bmcwebInsecureDisableXssPrevention == 0)
@@ -85,7 +85,7 @@ inline void addSecurityHeaders(const crow::Request& req [[maybe_unused]],
// If XSS is disabled, we need to allow loading from addresses other
// than self, as the BMC will be hosted elsewhere.
res.addHeader("Content-Security-Policy", "default-src 'none'; "
- "img-src *; "
+ "img-src * data:; "
"font-src *; "
"style-src *; "
"script-src *; "
对 Clear-Site-Data
的修改是为了避免如下错误:
Clear-Site-Data header on 'http://192.168.1.60/bmcweb/logout': Not supported for insecure origins.
对 Cross-Origin-Opener-Policy
的修改是为了避免如下错误:
The Cross-Origin-Opener-Policy header has been ignored, because the URL's
origin was untrustworthy.
It was defined either in the final response or a redirect. Please deliver
the response using the HTTPS protocol.
对 Content-Security-Policy
的修改是为了避免如下错误:
Refused to load the image 'data:image/svg+xml;' because it violates the
following Content Security Policy directive: "img-src *".
Note that '*' matches only URLs with network schemes ('http', 'https',
'ws', 'wss'), or URLs whose scheme matches `self`'s scheme.
The scheme 'data:' must be added explicitly.
webui-vue 前端
修改 webui-vue 前端根路径为 /bmcweb
:
diff --git a/vue.config.js b/vue.config.js
index de0ad12..1bd8fa0 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -93,6 +93,8 @@ module.exports = {
);
}
},
+ publicPath: '/bmcweb',
+ chainWebpack: (config) => config.optimization.minimize(false),
pluginOptions: {
i18n: {
localeDir: 'locales',
注意 chainWebpack
的配置只是为了方便调试而已。
同时设置 axios 的 baseURL
为 /bmcweb
:
diff --git a/src/store/api.js b/src/store/api.js
index 9fd900d..dca09e9 100644
--- a/src/store/api.js
+++ b/src/store/api.js
@@ -9,6 +9,7 @@ Axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
const api = Axios.create({
withCredentials: true,
+ baseURL: '/bmcweb',
});
api.interceptors.response.use(undefined, (error) => {
同时搜索 webui-vue 工程,将 wss://
修改成 ws://
,同时为 path 增加 /bmcweb/ws
前缀,如:
diff --git a/src/store/plugins/WebSocketPlugin.js b/src/store/plugins/WebSocketPlugin.js
index cbdc932..4713c6b 100644
--- a/src/store/plugins/WebSocketPlugin.js
+++ b/src/store/plugins/WebSocketPlugin.js
@@ -22,7 +22,8 @@ const WebSocketPlugin = (store) => {
process.env.VUE_APP_SUBSCRIBE_SOCKET_DISABLED === 'true' ? true : false;
if (socketDisabled) return;
const token = store.getters['authentication/token'];
- ws = new WebSocket(`wss://${window.location.host}/subscribe`, [token]);
+ // eslint-disable-next-line
+ ws = new WebSocket(`ws://${window.location.host}/bmcweb/ws/subscribe`, [token]);
ws.onopen = () => {
ws.send(JSON.stringify(data));
};
最终 URL 静态资源的前缀为 /bmcweb
,json api 的前缀为 /bmcweb
,websocket 的前缀为 /bmcweb/ws
。
nginx conf
nginx 的反向代理规则相应的改变如下(注意静态文件仍然由 bmcweb 后台进程直接提供服务):
# basic auth
set $authorization "Basic cm9vdDowcGVuQm1j";
# serve index.html & set cookie for / only
location ~ ^/bmcweb/?$ {
rewrite ^.*$ / break;
proxy_pass https://192.168.1.60:2443;
# fool the webui-vue, do not redirect to login page since we
# have set Authorization header explicitly
add_header Set-Cookie "IsAuthenticated=true; Path=/bmcweb";
}
# serve static assets & json api
location /bmcweb {
rewrite ^/bmcweb(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
# delete Secure flag
proxy_cookie_flags ~ nosecure;
proxy_set_header Authorization $authorization;
}
# serve websocket
location /bmcweb/ws {
rewrite ^/bmcweb/ws(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Authorization $authorization;
}
不过需要注意的是,即使通过 nginx 隐式传递了 Authorization
请求头,但是由于 webui-vue 前端页面需要在浏览器 Local storage 里读取 storedUsername
属性,因此仍然需要手工在浏览器中设置该属性为 root
(与 Basic cm9vdDowcGVuQm1j
对应)。
Set-Cookie
显式指定 Path=/bmcweb
属性,以避免如下的情况:
nginx serve static assets
需求
nginx 使用 HTTP 对外提供服务,通过 nginx 反向代理 bmcweb,且使用 nginx 为 webui-vue 提供静态资源服务。
bmcweb 后端和 webui-vue 前端都需要进行修改(相比上一需求的改动,改动点稍有不同),有两种解决方案,相应改动如下。
解决方案一
最终 URL 静态资源的前缀为 /bmcweb
,json api 的前缀为 /bmcweb/api
,websocket 的前缀为 /bmcweb/ws
。
1. bmcweb 后端
修改如下代码:
diff --git a/include/login_routes.hpp b/include/login_routes.hpp
index ae99757e..1fae5810 100644
--- a/include/login_routes.hpp
+++ b/include/login_routes.hpp
@@ -169,10 +169,10 @@ inline void handleLogin(const crow::Request& req,
asyncResp->res.addHeader(boost::beast::http::field::set_cookie,
"XSRF-TOKEN=" + session->csrfToken +
- "; SameSite=Strict; Secure");
+ "; SameSite=Strict; Secure; Path=/bmcweb");
asyncResp->res.addHeader(boost::beast::http::field::set_cookie,
"SESSION=" + session->sessionToken +
- "; SameSite=Strict; Secure; HttpOnly");
+ "; SameSite=Strict; Secure; HttpOnly; Path=/bmcweb");
// if content type is json, assume json token
asyncResp->res.jsonValue["token"] = session->sessionToken;
@@ -200,8 +200,8 @@ inline void handleLogout(const crow::Request& req,
"SESSION="
"; SameSite=Strict; Secure; HttpOnly; "
"expires=Thu, 01 Jan 1970 00:00:00 GMT");
- asyncResp->res.addHeader("Clear-Site-Data",
- R"("cache","cookies","storage")");
+ // asyncResp->res.addHeader("Clear-Site-Data",
+ // R"("cache","cookies","storage")");
persistent_data::SessionStore::getInstance().removeSession(session);
}
}
diff --git a/include/security_headers.hpp b/include/security_headers.hpp
index 1b9e984d..44009c51 100644
--- a/include/security_headers.hpp
+++ b/include/security_headers.hpp
@@ -60,7 +60,7 @@ inline void addSecurityHeaders(const crow::Request& req [[maybe_unused]],
res.addHeader("X-Permitted-Cross-Domain-Policies", "none");
res.addHeader("Cross-Origin-Embedder-Policy", "require-corp");
- res.addHeader("Cross-Origin-Opener-Policy", "same-origin");
+ res.addHeader("Cross-Origin-Opener-Policy", "unsafe-none");
res.addHeader("Cross-Origin-Resource-Policy", "same-origin");
if (bmcwebInsecureDisableXssPrevention == 0)
对 Clear-Site-Data
的修改是为了避免如下错误:
Clear-Site-Data header on 'http://192.168.1.60/bmcweb/api/logout': Not supported for insecure origins.
对 Cross-Origin-Opener-Policy
的修改是为了避免如下错误:
The Cross-Origin-Opener-Policy header has been ignored, because the URL's
origin was untrustworthy.
It was defined either in the final response or a redirect. Please deliver
the response using the HTTPS protocol.
注意这里为 XSRF-TOKEN
和 SESSION
两个 cookie 都显式设置了 Path
属性,这是因为接下来配置 nginx 反向代理时 json api 的前缀为 /bmcweb/api
,因此生成的 cookie Path
属性为 Path=/bmcweb/api
,会导致前端页面无法访问 cookie。
1. webui-vue 前端
修改 webui-vue 前端根路径为 /bmcweb
:
diff --git a/vue.config.js b/vue.config.js
index de0ad12..1bd8fa0 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -93,6 +93,8 @@ module.exports = {
);
}
},
+ publicPath: '/bmcweb',
+ chainWebpack: (config) => config.optimization.minimize(false),
pluginOptions: {
i18n: {
localeDir: 'locales',
注意 chainWebpack
的配置只是为了方便调试而已。
同时设置 axios 的 baseURL
为 /bmcweb/api
:
diff --git a/src/store/api.js b/src/store/api.js
index 9fd900d..4f41625 100644
--- a/src/store/api.js
+++ b/src/store/api.js
@@ -9,6 +9,7 @@ Axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
const api = Axios.create({
withCredentials: true,
+ baseURL: '/bmcweb/api',
});
api.interceptors.response.use(undefined, (error) => {
@@ -17,7 +18,7 @@ api.interceptors.response.use(undefined, (error) => {
// TODO: Provide user with a notification and way to keep system active
if (response.status == 401) {
if (response.config.url != '/login') {
- window.location = '/login';
+ window.location = '/bmcweb/#/login';
// Commit logout to remove XSRF-TOKEN cookie
store.commit('authentication/logout');
}
对路由处理的修改是因为跳转路径错误(可在加载过程中执行 logout 操作进行复现),需要注意对 response.config.url
的判断不能加上 baseURL
,此外 location
的值与 vue-router 的模式相关,webui-vue 默认使用的 hash
模式。
由于 cookie 的 Path
属性为 /bmcweb
,因此,相应的删除操作需要进行调整(否则无法删除):
diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js
index 0dca183..218ab03 100644
--- a/src/store/modules/Authentication/AuthenticanStore.js
+++ b/src/store/modules/Authentication/AuthenticanStore.js
@@ -29,7 +29,7 @@ const AuthenticationStore = {
state.authError = authError;
},
logout(state) {
- Cookies.remove('XSRF-TOKEN');
+ Cookies.remove('XSRF-TOKEN', { path: '/bmcweb' });
Cookies.remove('IsAuthenticated');
localStorage.removeItem('storedUsername');
state.xsrfCookie = undefined;
同时搜索 webui-vue 工程,将 wss://
修改成 ws://
,同时为 path 增加 /bmcweb/ws
前缀,如:
diff --git a/src/store/plugins/WebSocketPlugin.js b/src/store/plugins/WebSocketPlugin.js
index cbdc932..4713c6b 100644
--- a/src/store/plugins/WebSocketPlugin.js
+++ b/src/store/plugins/WebSocketPlugin.js
@@ -22,7 +22,8 @@ const WebSocketPlugin = (store) => {
process.env.VUE_APP_SUBSCRIBE_SOCKET_DISABLED === 'true' ? true : false;
if (socketDisabled) return;
const token = store.getters['authentication/token'];
- ws = new WebSocket(`wss://${window.location.host}/subscribe`, [token]);
+ // eslint-disable-next-line
+ ws = new WebSocket(`ws://${window.location.host}/bmcweb/ws/subscribe`, [token]);
ws.onopen = () => {
ws.send(JSON.stringify(data));
};
1. nginx conf
set $authorization "Basic cm9vdDowcGVuQm1j";
# serve index.html
location ~ ^/bmcweb/?$ {
rewrite ^.*$ /bmcweb/index.html last;
}
# serve static assets
location /bmcweb {
gzip_static always;
alias /home/runsisi/working/bmc/webui-vue/dist/;
# set cookie for / only
if ($uri = /bmcweb/index.html) {
# fool the webui-vue, do not redirect to login page since we
# have set Authorization header explicitly
add_header Set-Cookie "IsAuthenticated=true; Path=/bmcweb";
}
}
# serve json api
location /bmcweb/api {
rewrite ^/bmcweb/api(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
# delete Secure flag
proxy_cookie_flags ~ nosecure;
proxy_set_header Authorization $authorization;
}
# serve websocket
location /bmcweb/ws {
rewrite ^/bmcweb/ws(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Authorization $authorization;
}
解决方案二
最终 URL 静态资源的前缀为 /bmcweb
,json api 的前缀为 /bmcweb
,websocket 的前缀为 /bmcweb/ws
。
2. bmcweb 后端
修改如下代码:
diff --git a/include/login_routes.hpp b/include/login_routes.hpp
index ae99757e..ffc793de 100644
--- a/include/login_routes.hpp
+++ b/include/login_routes.hpp
@@ -200,8 +200,8 @@ inline void handleLogout(const crow::Request& req,
"SESSION="
"; SameSite=Strict; Secure; HttpOnly; "
"expires=Thu, 01 Jan 1970 00:00:00 GMT");
- asyncResp->res.addHeader("Clear-Site-Data",
- R"("cache","cookies","storage")");
+ // asyncResp->res.addHeader("Clear-Site-Data",
+ // R"("cache","cookies","storage")");
persistent_data::SessionStore::getInstance().removeSession(session);
}
}
diff --git a/include/security_headers.hpp b/include/security_headers.hpp
index 1b9e984d..44009c51 100644
--- a/include/security_headers.hpp
+++ b/include/security_headers.hpp
@@ -60,7 +60,7 @@ inline void addSecurityHeaders(const crow::Request& req [[maybe_unused]],
res.addHeader("X-Permitted-Cross-Domain-Policies", "none");
res.addHeader("Cross-Origin-Embedder-Policy", "require-corp");
- res.addHeader("Cross-Origin-Opener-Policy", "same-origin");
+ res.addHeader("Cross-Origin-Opener-Policy", "unsafe-none");
res.addHeader("Cross-Origin-Resource-Policy", "same-origin");
if (bmcwebInsecureDisableXssPrevention == 0)
对 Clear-Site-Data
的修改是为了避免如下错误:
Clear-Site-Data header on 'http://192.168.1.60/bmcweb/logout': Not supported for insecure origins.
对 Cross-Origin-Opener-Policy
的修改是为了避免如下错误:
The Cross-Origin-Opener-Policy header has been ignored, because the URL's
origin was untrustworthy.
It was defined either in the final response or a redirect. Please deliver
the response using the HTTPS protocol.
2. webui-vue 前端
修改 webui-vue 前端根路径为 /bmcweb
:
diff --git a/vue.config.js b/vue.config.js
index de0ad12..1bd8fa0 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -93,6 +93,8 @@ module.exports = {
);
}
},
+ publicPath: '/bmcweb',
+ chainWebpack: (config) => config.optimization.minimize(false),
pluginOptions: {
i18n: {
localeDir: 'locales',
注意 chainWebpack
的配置只是为了方便调试而已。
同时设置 axios 的 baseURL
为 /bmcweb
:
diff --git a/src/store/api.js b/src/store/api.js
index 9fd900d..4f41625 100644
--- a/src/store/api.js
+++ b/src/store/api.js
@@ -9,6 +9,7 @@ Axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
const api = Axios.create({
withCredentials: true,
+ baseURL: '/bmcweb',
});
api.interceptors.response.use(undefined, (error) => {
@@ -17,7 +18,7 @@ api.interceptors.response.use(undefined, (error) => {
// TODO: Provide user with a notification and way to keep system active
if (response.status == 401) {
if (response.config.url != '/login') {
- window.location = '/login';
+ window.location = '/bmcweb/#/login';
// Commit logout to remove XSRF-TOKEN cookie
store.commit('authentication/logout');
}
对路由处理的修改是因为跳转路径错误(可在加载过程中执行 logout 操作进行复现),需要注意对 response.config.url
的判断不能加上 baseURL
,此外 location
的值与 vue-router 的模式相关,webui-vue 默认使用的 hash
模式。
由于 bmcweb 后端生成的 cookie 没有设置 Path
属性,默认 Path
属性为 /bmcweb
,因此,相应的删除操作需要进行调整(否则无法删除):
diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js
index 0dca183..218ab03 100644
--- a/src/store/modules/Authentication/AuthenticanStore.js
+++ b/src/store/modules/Authentication/AuthenticanStore.js
@@ -29,7 +29,7 @@ const AuthenticationStore = {
state.authError = authError;
},
logout(state) {
- Cookies.remove('XSRF-TOKEN');
+ Cookies.remove('XSRF-TOKEN', { path: '/bmcweb' });
Cookies.remove('IsAuthenticated');
localStorage.removeItem('storedUsername');
state.xsrfCookie = undefined;
同时搜索 webui-vue 工程,将 wss://
修改成 ws://
,同时为 path 增加 /bmcweb/ws
前缀,如:
diff --git a/src/store/plugins/WebSocketPlugin.js b/src/store/plugins/WebSocketPlugin.js
index cbdc932..4713c6b 100644
--- a/src/store/plugins/WebSocketPlugin.js
+++ b/src/store/plugins/WebSocketPlugin.js
@@ -22,7 +22,8 @@ const WebSocketPlugin = (store) => {
process.env.VUE_APP_SUBSCRIBE_SOCKET_DISABLED === 'true' ? true : false;
if (socketDisabled) return;
const token = store.getters['authentication/token'];
- ws = new WebSocket(`wss://${window.location.host}/subscribe`, [token]);
+ // eslint-disable-next-line
+ ws = new WebSocket(`ws://${window.location.host}/bmcweb/ws/subscribe`, [token]);
ws.onopen = () => {
ws.send(JSON.stringify(data));
};
2. nginx conf
set $authorization "Basic cm9vdDowcGVuQm1j";
# serve index.html
location ~ ^/bmcweb/?$ {
rewrite ^.*$ /bmcweb/index.html last;
}
# serve index.html
location = /bmcweb/index.html {
gzip_static always;
alias /home/runsisi/working/bmc/webui-vue/dist/index.html;
# set cookie for / only
# fool the webui-vue, do not redirect to login page since we
# have set Authorization header explicitly
add_header Set-Cookie "IsAuthenticated=true; Path=/bmcweb";
}
# serve static assets
location ~ ^/bmcweb/(.+\.(?:css|js|svg|ico))$ {
gzip_static always;
alias /home/runsisi/working/bmc/webui-vue/dist/$1;
}
# serve json api
location /bmcweb {
rewrite ^/bmcweb(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
# delete Secure flag
proxy_cookie_flags ~ nosecure;
proxy_set_header Authorization $authorization;
}
# serve websocket
location /bmcweb/ws {
rewrite ^/bmcweb/ws(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Authorization $authorization;
}
2.1 nginx conf
由于后端服务可以自动忽略不支持的 Upgrade
请求头,因此也可以合并 json api 和 websocket 的处理(wss://
相应的 path 前缀改成 /bmcweb
)。
最终 URL 静态资源的前缀为 /bmcweb
,json api 的前缀为 /bmcweb
,websocket 的前缀为 /bmcweb
。
nginx 相应的配置如下:
set $authorization "Basic cm9vdDowcGVuQm1j";
# serve index.html
location ~ ^/bmcweb/?$ {
rewrite ^.*$ /bmcweb/index.html last;
}
# serve index.html
location = /bmcweb/index.html {
gzip_static always;
alias /home/runsisi/working/bmc/webui-vue/dist/index.html;
# set cookie for / only
# fool the webui-vue, do not redirect to login page since we
# have set Authorization header explicitly
add_header Set-Cookie "IsAuthenticated=true; Path=/bmcweb";
}
# serve static assets
location ~ ^/bmcweb/(.+\.(?:css|js|svg|ico))$ {
gzip_static always;
alias /home/runsisi/working/bmc/webui-vue/dist/$1;
}
# serve json api & websocket
location /bmcweb {
rewrite ^/bmcweb(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Authorization $authorization;
# delete Secure flag
proxy_cookie_flags ~ nosecure;
}
解决方案三
最终 URL 静态资源的前缀为 /bmcweb
,json api 的前缀为 /bmcweb/api
,websocket 的前缀为 /bmcweb/api
。
3. bmcweb 后端
修改如下代码:
diff --git a/include/login_routes.hpp b/include/login_routes.hpp
index ae99757e..1fae5810 100644
--- a/include/login_routes.hpp
+++ b/include/login_routes.hpp
@@ -169,10 +169,10 @@ inline void handleLogin(const crow::Request& req,
asyncResp->res.addHeader(boost::beast::http::field::set_cookie,
"XSRF-TOKEN=" + session->csrfToken +
- "; SameSite=Strict; Secure");
+ "; SameSite=Strict; Secure; Path=/bmcweb");
asyncResp->res.addHeader(boost::beast::http::field::set_cookie,
"SESSION=" + session->sessionToken +
- "; SameSite=Strict; Secure; HttpOnly");
+ "; SameSite=Strict; Secure; HttpOnly; Path=/bmcweb");
// if content type is json, assume json token
asyncResp->res.jsonValue["token"] = session->sessionToken;
@@ -200,8 +200,8 @@ inline void handleLogout(const crow::Request& req,
"SESSION="
"; SameSite=Strict; Secure; HttpOnly; "
"expires=Thu, 01 Jan 1970 00:00:00 GMT");
- asyncResp->res.addHeader("Clear-Site-Data",
- R"("cache","cookies","storage")");
+ // asyncResp->res.addHeader("Clear-Site-Data",
+ // R"("cache","cookies","storage")");
persistent_data::SessionStore::getInstance().removeSession(session);
}
}
diff --git a/include/security_headers.hpp b/include/security_headers.hpp
index 1b9e984d..44009c51 100644
--- a/include/security_headers.hpp
+++ b/include/security_headers.hpp
@@ -60,7 +60,7 @@ inline void addSecurityHeaders(const crow::Request& req [[maybe_unused]],
res.addHeader("X-Permitted-Cross-Domain-Policies", "none");
res.addHeader("Cross-Origin-Embedder-Policy", "require-corp");
- res.addHeader("Cross-Origin-Opener-Policy", "same-origin");
+ res.addHeader("Cross-Origin-Opener-Policy", "unsafe-none");
res.addHeader("Cross-Origin-Resource-Policy", "same-origin");
if (bmcwebInsecureDisableXssPrevention == 0)
对 Clear-Site-Data
的修改是为了避免如下错误:
Clear-Site-Data header on 'http://192.168.1.60/bmcweb/api/logout': Not supported for insecure origins.
对 Cross-Origin-Opener-Policy
的修改是为了避免如下错误:
The Cross-Origin-Opener-Policy header has been ignored, because the URL's
origin was untrustworthy.
It was defined either in the final response or a redirect. Please deliver
the response using the HTTPS protocol.
注意这里为 XSRF-TOKEN
和 SESSION
两个 cookie 都显式设置了 Path
属性,这是因为接下来配置 nginx 反向代理时 json api 的前缀为 /bmcweb/api
,因此生成的 cookie Path
属性为 Path=/bmcweb/api
,会导致前端页面无法访问 cookie。
3. webui-vue 前端
修改 webui-vue 前端根路径为 /bmcweb
:
diff --git a/vue.config.js b/vue.config.js
index de0ad12..7112321 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -88,11 +88,14 @@ module.exports = {
if (process.env.NODE_ENV === 'production') {
config.plugins.push(
new CompressionPlugin({
- deleteOriginalAssets: true,
+ deleteOriginalAssets: false,
})
);
}
},
+ outputDir: 'bmcweb',
+ publicPath: '/bmcweb',
+ chainWebpack: (config) => config.optimization.minimize(false),
pluginOptions: {
i18n: {
localeDir: 'locales',
对 deleteOriginalAssets
的修改是为了保留原始的未进行 gzip 压缩的资源文件,因为 nginx try_files
不支持 gzip_static
,必须要有源文件的存在,对 outputDir
的修改是为了将资源文件输出到 bmcweb
目录,方便 nginx 使用。
注意 chainWebpack
的配置只是为了方便调试而已。
同时设置 axios 的 baseURL
为 /bmcweb/api
:
diff --git a/src/store/api.js b/src/store/api.js
index 9fd900d..4f41625 100644
--- a/src/store/api.js
+++ b/src/store/api.js
@@ -9,6 +9,7 @@ Axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
const api = Axios.create({
withCredentials: true,
+ baseURL: '/bmcweb/api',
});
api.interceptors.response.use(undefined, (error) => {
@@ -17,7 +18,7 @@ api.interceptors.response.use(undefined, (error) => {
// TODO: Provide user with a notification and way to keep system active
if (response.status == 401) {
if (response.config.url != '/login') {
- window.location = '/login';
+ window.location = '/bmcweb/#/login';
// Commit logout to remove XSRF-TOKEN cookie
store.commit('authentication/logout');
}
对路由处理的修改是因为跳转路径错误(可在加载过程中执行 logout 操作进行复现),需要注意对 response.config.url
的判断不能加上 baseURL
,此外 location
的值与 vue-router 的模式相关,webui-vue 默认使用的 hash
模式。
由于 cookie Path
属性为 /bmcweb
,因此,相应的删除操作需要进行调整(否则无法删除):
diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js
index 0dca183..218ab03 100644
--- a/src/store/modules/Authentication/AuthenticanStore.js
+++ b/src/store/modules/Authentication/AuthenticanStore.js
@@ -29,7 +29,7 @@ const AuthenticationStore = {
state.authError = authError;
},
logout(state) {
- Cookies.remove('XSRF-TOKEN');
+ Cookies.remove('XSRF-TOKEN', { path: '/bmcweb' });
Cookies.remove('IsAuthenticated');
localStorage.removeItem('storedUsername');
state.xsrfCookie = undefined;
同时搜索 webui-vue 工程,将 wss://
修改成 ws://
,同时为 path 增加 /bmcweb/api
前缀,如:
diff --git a/src/store/plugins/WebSocketPlugin.js b/src/store/plugins/WebSocketPlugin.js
index cbdc932..575086c 100644
--- a/src/store/plugins/WebSocketPlugin.js
+++ b/src/store/plugins/WebSocketPlugin.js
@@ -22,7 +22,8 @@ const WebSocketPlugin = (store) => {
process.env.VUE_APP_SUBSCRIBE_SOCKET_DISABLED === 'true' ? true : false;
if (socketDisabled) return;
const token = store.getters['authentication/token'];
- ws = new WebSocket(`wss://${window.location.host}/subscribe`, [token]);
+ // eslint-disable-next-line
+ ws = new WebSocket(`ws://${window.location.host}/bmcweb/api/subscribe`, [token]);
ws.onopen = () => {
ws.send(JSON.stringify(data));
};
3. nginx conf
vue-router 为 hash
模式时的 nginx 配置:
location = /bmcweb {
return 301 /bmcweb/;
}
location ~ ^/bmcweb(.*)/index.html$ {
return 301 /bmcweb$1/;
}
# serve static assets
location /bmcweb/ {
root /home/runsisi/working/bmc/webui-vue;
try_files $uri $uri/index.html @bmcweb-index;
}
location @bmcweb-index {
return 301 /bmcweb/;
}
# serve json api & websocket
location /bmcweb/api {
rewrite ^/bmcweb/api(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# delete Secure flag
proxy_cookie_flags ~ nosecure;
}
如果更严谨一点,hash
模式下 nginx 配置如下:
location = /bmcweb {
return 301 /bmcweb/;
}
location ~ ^/bmcweb(.*)/index.html$ {
return 301 /bmcweb$1/;
}
# serve index.html
location = /bmcweb/ {
root /home/runsisi/working/bmc/webui-vue;
try_files /bmcweb/index.html =404;
}
# serve static assets
location /bmcweb/ {
root /home/runsisi/working/bmc/webui-vue;
try_files $uri @bmcweb-index;
}
location @bmcweb-index {
return 301 /bmcweb/;
}
# serve json api & websocket
location /bmcweb/api {
rewrite ^/bmcweb/api(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# delete Secure flag
proxy_cookie_flags ~ nosecure;
}
vue-router 为 history
模式时的 nginx 配置:
location = /bmcweb {
return 301 /bmcweb/;
}
location ~ ^/bmcweb(.*)/index.html$ {
return 301 /bmcweb$1/;
}
# serve static assets
location /bmcweb/ {
root /home/runsisi/working/bmc/webui-vue;
try_files $uri /bmcweb/index.html =404;
}
# serve json api & websocket
location /bmcweb/api {
rewrite ^/bmcweb/api(.*)$ $1 break;
proxy_pass https://192.168.1.60:2443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# delete Secure flag
proxy_cookie_flags ~ nosecure;
}
这里 nginx 的配置相比前面的方案去掉了 Authorization
头的处理,此外由于 vue 的 outputDir
进行了修改,因此相应的将 alias
修改为 root
。
最后修改于 2023-12-06