ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WSL+Alpine打造轻量SSH远程访问门户

WSL+Alpine打造轻量SSH远程访问门户

你有没有遇到过这样的场景:人不在电脑前,文件却都在Windows机器上,或者临时想改一个Linux脚本,手边只有手机和一台笔记本。想远程处理,最直接的思路是开Windows自带的OpenSSH服务。功能能用,但我一直不太乐意碰它——服务挂在系统层,和系统的账号体系、防火墙策略搅在一起,改一个配置都感觉是在动整台机器的安全边界。后来我换了个思路:把SSH服务从系统里搬出来,放到WSL里用Alpine Linux专门承担,Windows本身不做任何服务端改动。这套结构我用了大半年,资源占用低到可以忽略,坏了直接把发行版删了重建,一点都不影响Windows本体。

下面我把整个搭建过程完整过一遍,从Alpine在WSL里的安装初始化,到SSH服务的OpenRC配置和开机自启,再到局域网访问的两种网络方案和最后的密钥加固。如果你也想给Windows配一扇轻量、干净、随时能连的SSH门户,这篇可以直接照着抄。

1. 为什么选Alpine当这扇门,而不是Ubuntu

我先说结论:SSH门户这个角色,Alpine比Ubuntu更合适。原因要从"门户"的本质需求说起。

一扇门要做的事很简单:常年开着、占地少、坏了能快速换一扇。对应到技术上,就是服务常驻、资源占用低、环境可快速重建。SSH服务本身根本不重,核心依赖就是标准库和一套加密库,跑起来无论是CPU还是内存都微乎其微。Alpine的根基是musl libc,和Ubuntu那套glibc体系相比,同样的服务体积和内存会明显更小。实测在我这台机器上,一个Alpine发行版加OpenSSH全部装完,磁盘占用不到100MB,空闲时SSH进程组加起来内存占用也就三五十MB的量级,跑在Windows的WSL后面几乎感知不到它存在。

用表格对比一下常见的三个候选:

候选方案磁盘占用空闲内存服务管理远程环境对Windows影响
Alpine on WSL约80-100MB约30-50MBOpenRC完整Linux Shell几乎为零
Ubuntu on WSL1GB以上300MB以上systemd完整Linux Shell几乎为零
Windows内置OpenSSH系统自带依赖系统Windows服务PowerShell/CMD系统层直接暴露

三个方案其实都能用,差别在取舍。Windows内置OpenSSH的优势是零额外安装,但它把服务直接暴露在系统层,攻击面大,配置里能约束的粒度也不如Linux侧精细。Ubuntu on WSL适合要跑复杂开发任务的人,但作为一扇门来说它太重了,几十个后台服务跟着起来,很多根本用不上。Alpine则正好卡在"够用且最轻"的位置上,OpenSSH装完,系统里干净得只有必须的进程,排查问题也省心。

还有一个很实际的好处是隔离。门户这类服务是要长期监听端口、接受外部连接的,风险再小也是暴露在外的靶子。放到Alpine里,即使某天真被突破,攻击者拿到的也只是发行版内的普通用户权限,想横向影响Windows本身,难度要高得多。而重建成本,也就是wsl --unregister Alpine再重装一次的事,不用担心系统被改得千疮百孔。

当然,Alpine也有不适合的时候。如果你需要在这个环境里编译大项目、跑需要glibc特定行为的程序,或者依赖systemd管理一堆服务,那还是老老实实用Ubuntu。但如果你的需求和我一样,只是要一个随时能进的远程Linux入口,Alpine是我试过最舒服的选择。

2. WSL里装Alpine的两种方式与初始化细节

2.1 最快的方式:官方商店直接安装

如果你的WSL版本比较新,安装Alpine只需要两条命令。先在PowerShell里看下当前支持哪些发行版:

wsl --list --online

输出列表里找到Alpine,然后直接装:

wsl --install -d Alpine

装完系统会自动注册发行版,第一次启动会让你等一会儿初始化。之后用wsl -d Alpine进入,你会发现自己直接坐在root的shell里。这种方式省事,镜像来源也是官方渠道,适合大多数用户。

2.2 手动导入minirootfs:适合离线或洁癖场景

如果你在网络受限的环境,或者想保留一个完全干净的Alpine镜像自己折腾,可以走手动导入。先去Alpine官网下载对应架构的minirootfs压缩包,x86_64机器的文件名一般是alpine-minirootfs-3.20.3-x86_64.tar.gz。

在PowerShell里执行:

mkdir D:\WSL\Alpine wsl --import Alpine D:\WSL\Alpine D:\alpine-minirootfs-3.20.3-x86_64.tar.gz --version 2 wsl -d Alpine

手动导入的rootfs默认以root身份进入,而且没有设置密码。这里提醒一句,如果你打算让这个门户长期对外,第一件事就是把root密码设掉,否则等于把一扇没锁的门开着。

2.3 初始化:密码、普通用户、默认用户

进入Alpine后,先设置root密码:

passwd

然后更新软件源并装两个基础包:

apk update apk add sudo nano

接着创建一个日常使用的普通用户。我这里用dev做例子,你可以换成自己习惯的名字:

adduser -h /home/dev -s /bin/ash dev adduser dev wheel echo "%wheel ALL=(ALL) ALL" > /etc/sudoers.d/wheel

为什么要特意创建一个普通用户而不是直接用root?两个原因。一是SSH这扇门天天暴露在外面,接受root登录意味着多输一个密码就可能把整个系统的最高权限交出去,风险完全不对等;二是日常操作走普通用户,有什么异常还能在日志里看出是谁干的。Alpine默认shell是ash而不是bash,刚开始可能不习惯,习惯bash的话可以apk add bash装上再chsh -s /bin/bash dev切换。

最后写WSL的配置文件,让发行版默认从root变成普通用户:

# /etc/wsl.conf [user] default=dev

改完执行wsl --terminate Alpine然后在Windows侧重新wsl -d Alpine,你就会直接登进新用户了。

2.4 先确认WSL版本,免得后面踩坑

动手之前建议先跑一下wsl --version看看版本号。后面要用的镜像网络模式和[boot] command自启机制,都需要较新的WSL版本,Windows 11 22H2以上的系统基本都满足,但如果你还是Windows 10的旧版本,有些功能可能用不了,得提前规划好替代方案。

3. SSH服务安装、OpenRC配置与开机自启

3.1 装包加生成主机密钥,顺序不能错

在Alpine里装OpenSSH特别简单:

apk add openssh ssh-keygen -A

第二行很容易被忽略,但它其实很关键。Alpine的minirootfs或者官方镜像在首次启动时往往没有预生成主机密钥,而sshd在缺少主机密钥的情况下会直接拒绝启动,而且不会给什么友好的提示。ssh-keygen -A会自动把缺失的RSA、ECDSA、ED25519等主机密钥一次补齐,省去后面排查的麻烦。

装完之后先不要急着改配置,把服务拉起来验证一遍最基础的连接:

rc-service sshd start

3.2 sshd_config里值得改的几项

Alpine的/etc/ssh/sshd_config默认该有的都有,但需求不同,我建议在文件末尾追加下面这些覆盖项,不要动原本的默认配置,回头维护起来更清晰:

PermitRootLogin no PubkeyAuthentication yes PasswordAuthentication yes MaxAuthTries 3 ClientAliveInterval 300 ClientAliveCountMax 2

逐项解释一下:

  • PermitRootLogin no:禁止root直接登录,和前面创建普通用户呼应,这是安全底线。
  • PasswordAuthentication yes:先保留yes,方便你在配置密钥之前能通过密码先把通道打通。等密钥登录验证没问题之后,我会在最后一步把它改成no。
  • PubkeyAuthentication yes:开启公钥认证,下一步用。
  • MaxAuthTries 3:限制单次连接尝试次数,给暴力破解加一点阻力。
  • ClientAliveInterval 300 / ClientAliveCountMax 2:每5分钟发一次心跳检测,连续两次没回应就断开,省得一堆僵死会话占着连接。

改完配置重启:

rc-service sshd restart

3.3 OpenRC不是systemd,别用错命令

很多从Ubuntu过来的人会下意识敲systemctl,在Alpine里这会直接报错。Alpine默认的服务管理器是OpenRC,全套命令对应如下:

rc-service sshd status # 查看状态 rc-service sshd start # 启动 rc-service sshd restart # 重启 rc-service sshd stop # 停止 rc-update add sshd default # 加入默认运行级,开机自启

rc-update show可以查看当前已注册的自启服务列表。我提一句这个对应关系,是因为我在刚接触时在这上面卡过几分钟,明明装好了服务却一直报命令不存在,看着莫名烦躁。

3.4 “开机自启”在WSL里的正确姿势

这里要澄清一个概念:WSL不是一个真正意义上的开机,它是被wsl命令拉起时才启动的。所以所谓的"开机自启SSH",本质上是"发行版每次被启动时自动把sshd跑起来"。实现方式有两条路。

第一条,如果你的WSL版本支持,可以在/etc/wsl.conf里加:

[boot] command = rc-service sshd start

这样每次wsl -d Alpine启动发行版时,root会自动执行这条命令拉起SSH。

第二条,用Windows侧的计划任务:新建一个登录自启任务,程序指向wsl.exe,参数填-d Alpine -u root rc-service sshd start,触发器选"用户登录时"。这条路不依赖WSL版本,任何环境都能用。我当初就是因为WSL版本偏老,boot command还不支持,走的就是计划任务方案,后来升级之后两条路都试过,都能稳定把SSH带起来。

配置完成后的验证方法是:wsl --terminate Alpine,重新进入,然后立刻在Windows侧跑ssh dev@localhost。能连上就说明自启链路没问题。

4. 从局域网连进来:两种网络方案的选型与配置

4.1 WSL2的网络本质

WSL2跑在轻量虚拟机里,和Windows主机模型上是两台机器。它在自己的虚拟网段里有一个IP,Windows能通过这个IP访问它,但局域网的路由器和其他设备看不到这个虚拟网段。所以想让手机、另一台电脑连进来,必须在Windows侧做一道端口转发,或者换一种网络模式让WSL直接共享Windows的IP。

4.2 方案A:镜像网络模式,一条配置省掉所有转发

先用最简单的方案。如果你的系统是Windows 11 22H2以上版本,WSL也足够新,可以在用户目录下创建或编辑.wslconfig文件:

[experimental] networkingMode=mirrored

然后执行wsl --shutdown再重新启动发行版。启用镜像模式后,WSL会直接复用Windows的网络接口和IP,外部设备访问Windows的IP加端口,就等于访问WSL。配置好之后,直接在其他机器上执行:

ssh -p 22 dev@你的WindowsIP

理论上就能直达WSL里的sshd,不需要配置netsh转发,也不需要操心IP变化。

它的坑在于兼容性。实测在少数网络环境或者特定网关设备下,镜像模式可能出现访问异常,排起来比较费劲。如果你的Windows版本够新,第一优先尝试这个方案,不行再退回portproxy。

4.3 方案B:portproxy端口转发,兼容所有WSL2环境

这是传统的通用做法,思路很清晰:让Windows监听一个端口,把所有流量原样转发给WSL的22端口。

第一步,拿到WSL的IP。在PowerShell里执行:

wsl hostname -I

会输出一串IP,取第一个。我机器上一般是172开头的虚拟网段地址。

第二步,添加转发规则:

netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=22 connectaddress=<WSL的IP>

这里我把Windows这边监听端口设为2222而不是22,原因有两个:一是免得和Windows侧未来可能的其他服务冲突;二是非标准端口能挡掉一部分自动扫描脚本的碰运气试探。内部sshd仍跑在22,逻辑上更清晰。

第三步,放行防火墙。Windows新装的防火墙默认会拦掉这个入站端口,用管理员PowerShell执行:

New-NetFirewallRule -DisplayName "WSL SSH Portal" -Direction Inbound -Protocol TCP -LocalPort 2222 -Action Allow

如果更习惯传统命令行,用netsh advfirewall firewall add rule name="WSL SSH Portal" dir=in action=allow protocol=TCP localport=2222效果一样。

到这里,局域网其他设备就可以通过ssh -p 2222 dev@你的WindowsIP连进来了。

4.4 最关键的维护点:WSL的IP会变

portproxy方案最大的坑是connectaddress必须指向WSL当前的IP,而WSL2的虚拟网卡IP在每次重启后都可能变化。我已经遇到过不止一次:上午还能连,下午忽然被拒绝,一查是WSL的IP从172开头的某个地址变成了另一个,portproxy还傻傻指向旧地址。

解决办法是写一个小脚本,让Windows启动时把转发规则同步到当前IP。我用的是PowerShell:

$wslIp = (wsl.exe hostname -I).Trim().Split(' ')[0] if (-not $wslIp) { Write-Host "未能获取WSL IP"; exit 1 } netsh interface portproxy delete v4tov4 listenport=2222 listenaddress=0.0.0.0 2>$null netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=22 connectaddress=$wslIp netstat -an | findstr "2222"

把这段存成sync-wsl-ssh.ps1,丢到计划任务里,触发器选登录时执行,顺便设置重复任务间隔10分钟。这样每次WSL的IP变了,最多十分钟端口转发就会自动修正。

4.5 两个方案怎么选

对比维度镜像网络模式portproxy转发
系统要求Windows 11 22H2+任意WSL2
配置步骤编辑文件重启转发规则+防火墙+脚本
端口选择直接在22或改sshd配置可在Windows侧自由映射
IP变动无影响需要脚本同步
稳定性少数网关环境有兼容问题依赖脚本任务运行正常

我的建议很简单:版本新就先用镜像模式,不行马上退回portproxy;版本老也别纠结,直接portproxy加同步脚本,它反而是最稳的组合。两种方案同时开没必要,端口监听重叠会把自己绕晕。

5. 密钥登录、安全加固与故障速查

5.1 给钥匙换成密钥,密码只是临时过渡

远程登录长期用密码,爆破风险是不能忽视的。把SSH门上锁的正确方式是用公私钥。在你要发起连接的机器上生成一对密钥:

ssh-keygen -t ed25519 -C "wsl-alpine-portal"

一路回车即可。然后把公钥放到Alpine用户的家目录里,推荐用ssh-copy-id一把梭:

ssh-copy-id -p 2222 dev@你的WindowsIP

如果不方便跑这个命令,也可以手动操作:把本机的~/.ssh/id_ed25519.pub内容追加到Alpine里~/.ssh/authorized_keys,然后修正权限。

权限这一步千万不能省,这是SSH配置里最容易出问题的位置。sshd对密钥文件权限非常敏感:

chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys

我在初学时吃过这个亏:把公钥复制进去后死活还是要密码,查了半天日志,才发现~/.ssh整个目录是从root那边直接copy过来的,权限是755,sshd直接忽略了authorized_keys。这个问题的特征就是“密钥配置了,但不生效”。

密钥验证通过后,回到/etc/ssh/sshd_config把之前的PasswordAuthentication改成no,然后重启sshd。至此,密码登录这条路径就彻底关闭了。

5.2 上线前的加固清单

把下面几项过一遍,这套门户就基本能放心暴露在外网了:

  • 确认PermitRootLogin no,root不参与远程登录。
  • 确认PasswordAuthentication no,只接受密钥。
  • 可以加一行AllowUsers dev,只允许指定用户登录,哪怕系统里有其他账号也不怕被扫。
  • Windows侧只放行2222端口,不需要给WSL其他端口做转发,减少攻击面。
  • 密钥文件自己在本地备份好,万一Alpine整个删除重建,重新导入公钥只要一分钟。

我还会顺手看一眼认证日志里是否有异常登录尝试。这属于日常检查,不复杂,但能让你心里有数。

5.3 常见故障速查

现象可能原因处理办法
连接被拒绝sshd没启动,或Windows侧转发没生效WSL内rc-service sshd status;PowerShell里netsh interface portproxy show all看地址
密钥配置了还是要密码authorized_keys权限不对检查~/.ssh700、authorized_keys 600
Host key警告WSL重置后主机密钥变了在客户端known_hosts里删掉旧记录,重新连接
局域网连不上,本机能连防火墙未放行或portproxy指向旧IP检查防火墙规则;刷新转发脚本
改配置后服务起不来sshd_config语法错误用sshd -t校验语法,再rc-service sshd restart

这里补充一个排查思路:遇到网络连接问题,从内往外一层层定位。第一步在WSL里ssh dev@localhost确认本地通不通;第二步在Windows本机ssh dev@WSLIP确认Windows通不通;第三步再从局域网设备连。每一步都能把问题范围缩一半,比到处猜快得多。这套分层思路我用过很多次,碰到再诡异的问题都能快速找到症结。

最后再说一个我实际遇到的坑:手动导入的Alpine rootfs没有生成主机密钥,第一次启动sshd报了个含糊的错误就退出,查日志才看到no hostkeys的提示。所以只要是手动导入的发行版,ssh-keygen -A一定要先跑,这个顺序问题不解决,后面的所有配置都是白搭。

这套结构我稳定跑了小半年,期间因为想彻底清一次环境,把Alpine删掉重新导入了两次。每次重建从下载到SSH能连,最快不到十分钟就搞定。门户这类角色,我的体会是越轻越省心:Alpine加OpenSSH的资源占用几乎可以忽略,它却给了我一个完整的、随时能进的Linux入口。如果你也经常需要远程处理Windows机器上的事,不妨花半小时把这条路搭起来。记住两件事:密钥文件好好备份,WSL的IP同步脚本放计划任务里别删,之后基本就可以把它忘掉了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进