安全检查清单§
Unit 的核心将安全性作为其首要任务之一;我们的开发遵循适当的最佳实践,专注于使代码健壮可靠。然而,即使是最强化的系统也需要适当的设置、配置和维护。
本指南列出了从安装到各个应用配置保护 Unit 的步骤。
定期更新 Unit§
基本原理:每个版本都会引入错误修复和新功能,以提高安装的安全性。
操作:关注我们最新的新闻,并在新版本发布后不久升级到新版本。
安全套接字和状态§
基本原理:您的控制套接字和状态目录提供了对 Unit 配置的无限制访问,这需要严格保护。
操作:我们官方软件包中的默认配置通常足够;如果您使用其他安装方法,请确保控制套接字和状态目录是安全的。
Control Socket
If you use a UNIX control socket, ensure it is available to root only:
$ unitd -h
...
--control ADDRESS set address of control API socket
default: "unix:/default/path/to/control.unit.sock"
$ ps ax | grep unitd
... unit: main v1.32.1 [... --control /path/to/control.sock ...]
# ls -l /path/to/control.unit.sock
srw------- 1 root root 0 ... /path/to/control.unit.sock
UNIX domain sockets aren’t network accessible; for remote access, use NGINX or a solution such as SSH:
$ ssh -N -L ./here.sock:/path/to/control.unit.sock root@unit.example.com &
$ curl --unix-socket ./here.sock
{
"certificates": {},
"config": {
"listeners": {},
"applications": {}
}
}
If you prefer an IP-based control socket, avoid public IPs; they expose the control API and all its capabilities. This means your Unit instance can be manipulated by whoever is physically able to connect:
# unitd --control 203.0.113.14:8080
$ curl 203.0.113.14:8080
{
"certificates": {},
"config": {
"listeners": {},
"applications": {}
}
}
Instead, opt for the loopback address to ensure all access is local to your server:
# unitd --control 127.0.0.1:8080
$ curl 203.0.113.14:8080
curl: (7) Failed to connect to 203.0.113.14 port 8080: Connection refused
However, any processes local to the same system can access the local socket, which calls for additional measures. A go-to solution would be using NGINX to proxy Unit’s control API.
State Directory
The state directory stores Unit’s internal configuration between launches. Avoid manipulating it or relying on its contents even if tempted to do so. Instead, use only the control API to manage Unit’s configuration.
Also, the state directory should be available only to root (or the user that the main process runs as):
$ unitd -h
...
--state DIRECTORY set state directory name
default: "/default/path/to/unit/state/"
$ ps ax | grep unitd
... unit: main v1.32.1 [... --state /path/to/unit/state/ ...]
# ls -l /path/to/unit/state/
drwx------ 2 root root 4096 ...
配置 SSL/TLS§
基本原理:为保护您的客户端连接在生产场景中,为您的 Unit 安装配置 SSL 证书包。
操作:有关详细信息,请参阅SSL/TLS 证书和TLS with Certbot。
对您的路由进行错误验证§
基本原理:可以说,路由是 Unit 配置中最灵活且用途最广泛的部分。因此,它们必须尽可能清晰且健壮,以避免出现漏洞和巨大缺陷。
保护应用程序数据§
基本原理:Unit 的架构涉及在应用程序交付期间一起操作的许多进程;不当的进程权限可能会使敏感文件跨应用程序甚至公开可用。
操作:正确配置您的应用程序目录和共享:应用程序和路由器进程需要访问它们。不过,避免使用臭名昭著的777等宽松权限,而应根据需要分配权限。
File Permissions
To configure file permissions for your apps, check Unit’s build-time and run-time options first:
$ unitd -h
...
--user USER set non-privileged processes to run as specified user
default: "unit_user"
--group GROUP set non-privileged processes to run as specified group
default: user's primary group
$ ps ax | grep unitd
... unit: main v1.32.1 [... --user unit_user --group unit_group ...]
In particular, this is the account the router process runs as. Use this information to set up permissions for the app code or binaries and shared static files. The main idea is to limit each app to its own files and directories while simultaneously allowing Unit’s router process to access static files for all apps.
Specifically, the requirements are as follows:
All apps should run as different users so that the permissions can be configured properly. Even if you run a single app, it’s reasonable to create a dedicated user for added flexibility.
An app’s code or binaries should be reachable for the user the app runs as; the static files should be reachable for the router process. Thus, each part of an app’s directory path must have execute permissions assigned for the respective users.
An app’s directories should not be available to other apps or non-privileged system users. The router process should be able to access the app’s static file directories. Accordingly, the app’s directories must have read and execute permissions assigned for the respective users.
The files and directories that the app is designed to update should be writable only for the user the app runs as.
The app code should be readable (and executable in case of external apps) for the user the app runs as; the static content should be readable for the router process.
A detailed walkthrough to guide you through each requirement:
If you have several independent apps, running them with a single user account poses a security risk. Consider adding a separate system user and group per each app:
# useradd -M app_user # groupadd app_group # usermod -L app_user # usermod -a -G app_group app_user
Even if you run a single app, this helps if you add more apps or need to decouple permissions later.
It’s important to add Unit’s non-privileged user account to each app group:
# usermod -a -G app_group unit_user
Thus, Unit’s router process can access each app’s directory and serve files from each app’s shares.
A frequent source of issues is the lack of permissions for directories inside a directory path needed to run the app, so check for that if in doubt. Assuming your app code is stored at /path/to/app/:
# ls -l / drwxr-xr-x some_user some_group path # ls -l /path/ drwxr-x--- some_user some_group to
This may be a problem because the to/ directory isn’t owned by app_user:app_group and denies all permissions to non-owners (as the --- sequence tells us), so a fix can be warranted:
# chmod o+rx /path/to/
Another solution is to add app_user to some_group (assuming this was not done before):
# usermod -a -G some_group app_user
Having checked the directory tree, assign ownership and permissions for your app’s directories, making them reachable for Unit and the app:
# chown -R app_user:app_group /path/to/app/ # chown -R app_user:app_group /path/to/static/app/files/ # find /path/to/app/ -type d -exec chmod u=rx,g=rx,o= {} \; # find /path/to/static/app/files/ -type d -exec chmod u=rx,g=rx,o= {} \;
If the app needs to update specific directories or files, make sure they’re writable for the app alone:
# chmod u+w /path/to/writable/file/or/directory/
In case of a writable directory, you may also want to prevent non-owners from messing with its files:
# chmod +t /path/to/writable/directory/
Note
Usually, apps store and update their data outside the app code directories, but some apps may mix code and data. In such a case, assign permissions on an individual basis, making sure you understand how the app uses each file or directory: is it code, read-only content, or writable data.
For embedded apps, it’s usually enough to make the app code and the static files readable:
# find /path/to/app/code/ -type f -exec chmod u=r,g=r,o= {} \; # find /path/to/static/app/files/ -type f -exec chmod u=r,g=r,o= {} \;
For external apps, additionally make the app code or binaries executable:
# find /path/to/app/ -type f -exec chmod u=rx,g=rx,o= {} \; # find /path/to/static/app/files/ -type f -exec chmod u=r,g=r,o= {} \;
To run a single app, configure Unit as follows:
{ "listeners": { "*:80": { "pass": "routes" } }, "routes": [ { "action": { "share": "/path/to/static/app/files/$uri", "fallback": { "pass": "applications/app" } } } ], "applications": { "app": { "type": "...", "user": "app_user", "group": "app_group" } } }
To run several apps side by side, configure them with appropriate user and group names. The following configuration distinguishes apps based on the request URI, but you can implement another scheme such as different listeners:
{ "listeners": { "*:80": { "pass": "routes" } }, "routes": [ { "match": { "uri": "/app1/*" }, "action": { "share": "/path/to/static/app1/files/$uri", "fallback": { "pass": "applications/app1" } } }, { "match": { "uri": "/app2/*" }, "action": { "share": "/path/to/static/app2/files/$uri", "fallback": { "pass": "applications/app2" } } } ], "applications": { "app1": { "type": "...", "user": "app_user1", "group": "app_group1" }, "app2": { "type": "...", "user": "app_user2", "group": "app_group2" } } }
Note
As usual with permissions, different steps may be required if you use ACLs.
App Internals
Unfortunately, quite a few web apps are built in a manner that mixes their source code, data, and configuration files with static content, which calls for complex access restrictions. The situation is further aggravated by the inevitable need for maintenance activities that may leave a footprint of extra files and directories unrelated to the app’s operation. The issue has several aspects:
Storage of code and data at the same locations, which usually happens by (insufficient) design. You neither want your internal data and code files to be freely downloadable nor your user-uploaded data to be executable as code, so configure your routes and apps to prevent both.
Exposure of configuration data. Your app-specific settings, .ini or .htaccess files, and credentials are best kept hidden from prying eyes, and your routing configuration should reflect that.
Presence of hidden files from versioning, backups by text editors, and other temporary files. Instead of carving your configuration around these, it’s best to keep your app free of them altogether.
If these can’t be avoided, investigate the inner workings of the app to prevent exposure, for example:
{
"routes": {
"app": [
{
"match": {
"uri": [
"*.php",
"*.php/*"
]
},
"action": {
"pass": "applications/app/direct"
}
},
{
"match": {
"uri": [
"!/sensitive/*",
"!/data/*",
"!/app_config_values.ini",
"!*/.*",
"!*~"
]
},
"action": {
"share": "/path/to/app/static$uri",
"types": [
"image/*",
"text/*",
"application/javascript"
],
"fallback": {
"pass": "applications/app/index"
}
}
}
]
}
}
However, this does not replace the need to set up file permissions; use both matching rules and per-app user permissions to manage access. For more info and real-life examples, refer to our app howtos and the ‘File Permissions’ callout above.
Unit's Process Summary
Unit’s processes are detailed elsewhere, but here’s a synopsis of the different roles they have:
Process |
Privileged? |
User and Group |
Description |
---|---|---|---|
Main |
Yes |
Whoever starts the unitd executable; by default, root. |
Runs as a daemon, spawning Unit’s non-privileged and app processes; requires numerous system capabilities and privileges for operation. |
Controller |
No |
Set by |
Serves the control API, accepting reconfiguration requests, sanitizing them, and passing them to other processes for implementation. |
Discovery |
No |
Set by |
Discovers the language modules in the module directory at startup, then quits. |
Router |
No |
Set by |
Serves client requests, accepting them, processing them on the spot, passing them to app processes, or proxying them further; requires access to static content paths you configure. |
App processes |
No |
Set by per-app user and group
options; by default,
|
Serve client requests that are routed to apps; require access to paths and namespaces you configure for the app. |
You can check all of the above on your system when Unit is running:
$ ps aux | grep unit
...
root ... unit: main v1.32.1
unit ... unit: controller
unit ... unit: router
unit ... unit: "front" application
The important outtake here is to understand that Unit’s non-privileged processes don’t require running as root. Instead, they should have the minimal privileges required to operate, which so far means the ability to open connections and access the application code and the static files shared during routing.
精简调试和访问日志§
基本原理:单元在常规日志和访问日志中存储潜在的敏感数据;如果启用调试模式,其大小也可能成为问题。
操作:保护对日志的访问,确保它们不超过允许的磁盘空间。
详细信息
单元可以维护两个不同的日志
默认情况下启用的通用日志,可以切换到调试模式以提高详细程度。
默认情况下关闭的访问日志,可以通过控制 API 启用。
如果您启用调试模式或访问日志记录,请使用 logrotate 等工具轮换这些日志,以避免过度增长。一个示例 logrotate 配置
/path/to/unit.log {
daily
missingok
rotate 7
compress
delaycompress
nocreate
notifempty
su root root
postrotate
if [ -f /path/to/unit.pid ]; then
/bin/kill -SIGUSR1 `cat /path/to/unit.pid`
fi
endscript
}
找出日志和 PID 文件路径
$ unitd -h
...
--pid FILE set pid filename
default: "/default/path/to/unit.pid"
--log FILE set log filename
default: "/default/path/to/unit.log"
$ ps ax | grep unitd
... unit: main v1.32.1 [... --pid /path/to/unit.pid --log /path/to/unit.log ...]
另一个问题是日志的可访问性。日志由 主进程 打开和更新,该进程通常以 root 身份运行。但是,为了让特定使用者可以使用它们,您可能需要为使用者运行的专用用户启用访问权限。
也许,实现此目的最直接的方法是将日志所有权分配给使用者的帐户。假设您有一个以 log_user:log_group 身份运行的日志实用程序
# chown log_user:log_group /path/to/unit.log
# curl -X PUT -d '"/path/to/access.log"' \
--unix-socket /path/to/control.unit.sock \
http://localhost/config/access_log
{
"success": "Reconfiguration done."
}
# chown log_user:log_group /path/to/access.log
如果您更改日志文件所有权,请相应地调整 logrotate 设置
/path/to/unit.log {
...
su log_user log_group
...
}
注意
与权限一样,如果您使用 ACL,则可能需要不同的步骤。
添加限制、隔离§
基本原理:如果底层操作系统允许,单元提供一些功能,为您的应用创建额外的隔离和包含级别,例如