ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GitLab root密码重置全攻略:Rails console与数据库兜底

GitLab root密码重置全攻略:Rails console与数据库兜底 周一早上我打开电脑准备合代码git push弹出来要求输入 GitLab 账号密码我顺手敲了 root 的密码一次没过两次没过三次还是没过。一开始以为键盘又抽风了换了个终端试还是提示Invalid login or password。这时候我意识到一个扎心的事实整个公司代码仓库的 root 密码被我忘了。这事放在个人电脑上重置一下也就几分钟放在自己搭的 GitLab 上就有点尴尬了——因为所有项目的最高管理员权限都握在 root 手里你进不去管理后台很多操作都做不了。后来我把常见的重置思路完整走了一遍Rails console、数据库兜底、2FA 干扰、账户锁定、LDAP 接管……踩了不少坑才顺利把密码找回来。这篇文章就是针对忘记 GitLab root 密码这件事的完整处理记录。不管你是运维、后端开发还是临时被拉去维护代码平台的人只要手头的 GitLab 是自建的遇到过 root 登不进去的情况下面的内容都可以直接参考。1. 为什么一个密码会有这么多重置方式先认清GitLab的认证体系1.1 root究竟是个什么角色GitLab 里的 root 不是 Linux 系统账号而是 GitLab 应用内置的超级管理员账号默认用户名就是 rootID 一般是 1。它拥有整个 GitLab 实例的最高权限能管理用户、项目、群组、CI/CD Runner、系统设置、License还能通过 API 做任何操作。所以 root 密码一旦丢失等于你被关在了自家门口。很多人会把 GitLab 的 root 和服务器上的 Linux root 搞混这两个完全是两回事。GitLab root 是应用层用户登录的是 GitLab Web 界面、API 和 SSH 仓库操作Linux root 是操作系统账号登录的是服务器系统本身。如果哪天服务器 ssh 进不去那是 Linux 密码的问题走单用户模式重置和本文说的不是一条路线。1.2 密码在GitLab里是怎么存的、怎么验证的理解了密码的存储和验证机制后面所有重置方案就都顺理成章了。GitLab 的用户数据存在users表里密码字段叫encrypted_password。它并不是明文也不是普通 MD5/SHA而是用了 bcrypt 算法带随机盐值每次生成同一个密码的哈希结果都不一样。所以你不能直接把数据库里的密文翻译成明文只能覆盖成新的哈希。当你登录时GitLab 会接收输入的密码通过 Rails 的 Devise 认证框架调用 BCrypt 做比对匹配则登录成功。但如果你的 GitLab 配置了 LDAP、SAML、CAS 等外部认证登录流程会被接管走的是企业统一身份认证本地密码在这个场景下就被跳过了。这一点很重要因为很多密码明明改了还是登不进去的情况根因根本不是密码本身而是认证链路变了。1.3 为什么密码会突然不对根据我自己的经验root 密码失效通常有几种情况现象可能原因排查方向一直用旧密码突然登不进去别人改过密码 / 账号被锁定 / 2FA 生效看users表的failed_attempts、locked_at密码没改过但总是提示密码错LDAP 接管 / 管理员重置过查ldap_blocked、state字段新装的 GitLab初始密码不知道初始密码随机生成仅存在本地文件里查/etc/gitlab/initial_root_password修改后依然登录失败改错数据库 / 2FA 未关确认连接的是主库确认 OTP 状态有一个细节必须提一下GitLab 新装完成后root 的初始密码会写在/etc/gitlab/initial_root_password文件里但这个文件在首次登录后 24 小时会被自动删除。如果你的 GitLab 是别人装的密码又没交接那基本就只能走重置流程了。较老的版本11.4 之前默认初始密码是5iveL!fe虽然早就停产了但如果你维护的是一个老古董实例这串密码值得先试一次。1.4 先分清楚GitLab root和Linux root不是一回事再强调一遍因为这个问题太容易踩了。如果你在服务器上执行passwd root改的是系统账号密码对 GitLab 登录毫无影响。同理你在 GitLab 里改 root 密码也影响不了服务器 ssh 登录。两个人共享一个root名字但分属两个完全不同的账号体系。如果你要解决的问题是服务器 ssh 进不去应该走 Linux 密码重置路线比如单用户模式、live CD 之类的如果问题是GitLab 网页登录失败那才是本文下面要讲的重置方案。先确认问题在哪一层别把精力花错地方。2. 标准解法用gitlab-rails console把root密码重新写回去2.1 进入Rails console之前需要确认的事官方推荐的重置方式是通过gitlab-rails console进入 Rails 交互环境直接操作 User 模型。这个方法的好处是走完了 Rails 层的完整校验逻辑比如密码长度、确认密码一致性、bcrypt 加密等不会留下数据库里密码字段改了但格式不对的隐患。在动手之前先想清楚三件事第一确认你有服务器主机的 shell 权限。因为 console 只能在 GitLab 服务器本机执行除非你开启了某种远程终端否则必须能 ssh 上服务器或者能执行docker exec进入 GitLab 容器。第二确认 GitLab 的部署方式。Omnibus 包安装、Docker 容器、源码安装这三者的进入命令差别很大下面会分别给。第三如果 GitLab 是跑在 Docker 里的还要确认容器启动时是否挂了数据卷。如果当初docker run没有把/var/opt/gitlab目录持久化到宿主机容器一删一建数据库全没了root 密码自然也会回到初始状态。这时候你重置密码没有意义得先把数据卷挂好再谈登录的事。2.2 Docker部署、Omnibus部署、源码部署三种进入方式Docker 部署是目前最常见的步骤是# 先找到GitLab容器名 docker ps | grep gitlab # 进入容器 docker exec -it gitlab bash # 在容器内执行 gitlab-rails console production这里有个坑如果容器名不同把gitlab换成你实际的容器名。进入容器后推荐显式加上production参数避免个别环境加载了错误的 Rails 环境。Omnibus 包也就是gitlab-ce/gitlab-ee官方安装包部署的不用进容器直接sudo gitlab-rails console production这个命令会启动一个 Rails 交互终端启动过程可能要等十几秒到半分钟因为需要加载整个 GitLab 应用环境不要以为卡死了。源码部署的情况比较少见一般是早期用gitlab-shell手工搭建的那批用户cd /home/git/gitlab sudo -u git -H bin/rails console production无论是哪种方式成功进入后都应该看到类似irb(main):001:0的提示符。2.3 在console里的完整操作与验证进入 console 后建议先确认当前环境确实是 productionRails.env输出应该是production。如果输出是development或test说明环境加载有问题终止操作重新检查命令。接下来先找到 root 用户user User.find_by(username: root)这里不建议用User.find(1)因为某些从旧版本升级或数据迁移过的实例root 的 ID 不一定是 1用用户名查找更可靠。找到后可以确认一下用户状态user.state user.active? user.ldap_blocked?然后设置新密码user.password YourNewPassword123! user.password_confirmation YourNewPassword123! user.save!注意一定要用save!而不是save。两者区别在于save!在保存失败时会直接抛出异常并打印具体的错误原因比如密码太短、复杂度不够等save只是返回true或false不告诉你哪里不对。如果你设的密码不满足 GitLab 的密码策略save!会明确报出来方便你及时调整。如果保存时报了ActiveRecord::RecordInvalid可以通过以下方式看具体错误user.errors.full_messages保存成功后可以在 console 里直接验证密码是否生效user.valid_password?(YourNewPassword123!)返回true说明密码已经正确写入。接着退出 consoleexit然后打开浏览器用 root 加新密码登录正常情况下就能进去了。2.4 顺手把锁定的痕迹一并清掉很多人改完密码后还是登不进去一查才发现是账号被锁了。GitLab 的用户账户有连续登录失败锁定机制超过一定次数后failed_attempts字段会增加locked_at会被置为当前时间账户直接拒绝登录输入正确密码也没用。所以进入 console 后尤其是确认账号之前有过多次登录失败记录的建议把这几行一起执行掉user.failed_attempts 0 user.locked_at nil user.unlock_token nil user.save!这四行代码清除了锁定状态。很多实战教程只说改密码没有提这一层导致有人改了密码还是被锁在外面我当时就吃过这个亏。另外如果 root 用户之前的状态不是 active比如被手动 block 了还需要把状态恢复user.state active user.save!执行完这些操作后再退出 console 验证登录会更加稳妥。3. 数据库兜底绕开Rails直接改bcrypt哈希的操作细节3.1 这个方案的本质是什么、什么时候才需要它Rails console 方案虽然官方但有一个前提Rails 应用本身能正常加载。如果 GitLab 因为某些原因比如代码升级失败、数据库连接异常、内存不足导致gitlab-rails console根本起不来那 console 这条路就堵死了。这时候还有一条兜底路线直接操作数据库修改users表里的encrypted_password字段。这个方案的本质就是手工生成一个合法的 bcrypt 哈希并且覆盖掉原来的密码字段。它绕过了 Rails 层的所有校验所以风险更高操作必须仔细。它的适用场景是你能连上 GitLab 的数据库但 Rails 控制台用不了或者你想在极短时间内完成密码替换不想等 console 慢慢加载。注意直接改数据库属于绕过应用层的底层操作动手之前务必先备份原字段防止改坏了恢复不回来。后面会详细说。3.2 先备份当前用户记录防止改坏连接数据库之后第一件事不是改数据而是把当前 root 用户的完整信息、尤其是原始的encrypted_password查出来备份好SELECT id, username, email, encrypted_password, state, failed_attempts, locked_at, otp_required_for_login FROM users WHERE username root;把查询结果完整复制保存到本地文件里。万一新哈希写进去后出了意外你还能用原来的哈希恢复旧密码状态。3.3 生成一个符合Rails预期的bcrypt哈希这一步最容易踩坑既然 password 字段存的是 bcrypt 哈希那就不能随便写个字符串进去。你必须生成一个符合 Rails/Devise 预期的哈希格式。生成哈希的方式有三种我按推荐度排序方式一在 GitLab 服务器上用gitlab-rails runner生成前提是 Rails 环境能起sudo gitlab-rails runner puts BCrypt::Password.create(YourNewPassword123!)Docker 部署则是docker exec -it gitlab gitlab-rails runner puts BCrypt::Password.create(YourNewPassword123!)这条命令执行后会输出一串类似$2a$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx的字符串这就是我们要的新哈希。$2a$前缀是和 Rails 使用的 bcrypt 兼容的格式。方式二在任意一台装有 Python 3 和 bcrypt 库的机器上生成推荐因为你本机电脑通常就能用python3 -c import bcrypt; print(bcrypt.hashpw(bYourNewPassword123!, bcrypt.gensalt(rounds10, prefixb2a)).decode())如果提示没有 bcrypt 库先执行pip3 install bcrypt注意prefixb2a这个参数它强制生成$2a$开头的哈希。bcrypt 有多个版本前缀比如$2y$、$2b$GitLab 底层的 bcrypt gem 对$2a$的兼容性最稳定。如果你用默认参数生成可能会有$2b$前缀稳妥起见还是显式指定 2a。方式三用 htpasswd 工具生成但需要额外确认前缀htpasswd -bnBC 10 YourNewPassword123! | tr -d :\n这个方法的问题在于 htpasswd 生成的通常是$2y$前缀老版本的 bcrypt gem 未必能正确读取。再加上很多服务器默认没装 apache2-utils用的时候还要装包性价比不高。如果你手头正好有 htpasswd也不是不能用但生成后最好用文本编辑器把$2y$手动替换成$2a$否则有兼容性风险。这里我用了一个比较稳妥的策略优先用 gitlab-rails runner 或 Python bcrypt 生成这两种方式都能保证前兼容性。hash 的 cost 设置为 10 即可GitLab 默认强度一般在 10 上下太低容易让密码更容易被碰撞太高则每次登录验证变慢没必要。3.4 连接数据库执行UPDATE并核验效果生成哈希之后需要连接 GitLab 的数据库执行更新。如果你是 Omnibus 部署GitLab 自带 PostgreSQL直接sudo gitlab-psql -d gitlabhq_productionDocker 部署的容器内执行docker exec -it gitlab gitlab-psql -d gitlabhq_production进入 psql 后执行UPDATE users SET encrypted_password 把这里替换成刚生成的哈希, otp_required_for_login false, failed_attempts 0, locked_at NULL, unlock_token NULL WHERE username root;这里有三个附加条件为什么要一起写进去我解释一下第一otp_required_for_login false。如果之前 root 开启了 2FA而你手上没有 OTP 验证器或者恢复码那就算密码改对了登录时依然会卡在二次验证这一关。直接关掉 OTP登录流程就恢复为纯密码验证。第二failed_attempts 0、locked_at NULL、unlock_token NULL。这是为了清除账户锁定状态和前面 console 方案里清锁定是一个道理。第三WHERE username root而不是WHERE id 1。原因同上用用户名定位最保险。执行后检查受影响行数正常应该是 1。如果返回 0说明用户名写错了或者表里没有这个用户仔细检查再执行。完成后退出 psql打开浏览器登录。如果还是登不进去回到下一步排查大概率是下面几个问题之一。4. 改完密码依然登不进去这五个细节最坑4.1 2FA还开着密码对了也进不去这是所有坑里出现频率最高的一个。症状非常迷惑你确定密码已经改成功了console 里valid_password?也返回true但网页登录时输入密码依然进不去——因为它要求你输入 6 位 OTP 验证码而你根本没有绑定过或者手机换过了。每个人看到 2FA 页面时都会愣一下因为提示语往往就是请输入两步验证码并没有告诉你 root 之前绑过验证器。我的处理方法是在 console 里直接关闭 OTP 需求再重置密码user.otp_required_for_login false user.save!如果是数据库直改方案就是我上面 SQL 里写的那一行otp_required_for_login false。这个动作在重置密码时非常重要很多人偏偏漏掉。4.2 账户多次输错被锁定症状输入密码后GitLab 直接提示您的账户已被锁定或者只提示密码错误但 console 里valid_password?是true。原因很简单在你尝试各种密码的过程中连续失败次数超过了 GitLab 的阈值默认是 10 次账户被自动锁定了。解决办法就是在 console 里清除锁定字段user.failed_attempts 0 user.locked_at nil user.unlock_token nil user.save!也可以直接通过数据库更新UPDATE users SET failed_attempts 0, locked_at NULL, unlock_token NULL WHERE username root;清掉之后马上再试登录大概率就能过了。我的建议是不管你有没有意识到自己在反复试密码重置完成后顺手把这三个字段清一遍就当是无害操作。4.3 LDAP接管了登录本地密码形同虚设有些公司自建 GitLab 时配置了 LDAP 或企业统一认证登录页面上看起来还是同一个登录框但后台认证逻辑已经完全交给 LDAP 了。这时候 root 用户可能有两个结果一是登录页直接变成统一身份认证的入口二是 root 本身的ldap_blocked字段被置为true本地密码完全被禁用。如果是后者症状非常迷惑你在 console 里把密码改得明明白白保存成功valid_password?也返回true但网页登录时死活进不去。处理办法进入 console把 LDAP 阻断状态手动解除user.ldap_blocked false user.save!但要提醒一句如果企业策略强制 LDAP 认证并且 GitLab 开启了 LDAP 同步定时任务这个字段可能过一会儿又被系统自动置回true。测试环境怎么玩都行生产环境遇到这种情况最靠谱的做法是先跟负责账号体系的同事确认 LDAP 端 root 的状态或者临时把登录策略改成本地认证优先再做密码重置。这事看起来是技术问题实际上是流程问题别自己闷头硬改。4.4 改错数据库一看是只读副本或外部MySQL还有一种情况SQL 执行成功了受影响行数也是 1但登录还是提示密码错误。这就要怀疑你改的数据库到底是不是 GitLab 正在用的那个库。一个典型场景GitLab 的高可用架构里应用连接的是读写分离的数据库。你在从库上执行了UPDATE但应用实际读写走的是主库或者其他副本数据根本没同步过去。另一个典型场景GitLab 配置了外部数据库比如云 RDS 上的 MySQL/PostgreSQL而不是自带的 PostgreSQL。你在服务器上执行sudo gitlab-psql进去的可能是默认的本地库而这个本地库根本不是应用使用的库。排查方法sudo grep -E db_host|db_port|db_database|db_username /etc/gitlab/gitlab.rb看看配置里写的是哪个数据库连接信息然后通过对应的连接方式去修改。还有一个容易忽略的点如果你用的是 Docker 部署但容器启动时没有挂载/var/opt/gitlab数据卷容器一旦重建所有数据包括你刚改好的密码都会消失回到最初的初始密码状态。所以排查时也顺手看看容器挂载情况docker inspect gitlab | grep -A 20 Mounts如果发现没有挂载数据卷优先解决持久化问题再谈密码重置。4.5 密码策略、Rails环境、旧缓存这些隐藏变量最后几个坑比较隐蔽但也不少见。第一个是密码策略挡住。GitLab 管理后台可以设置最小密码长度默认是 8 位。如果你设的新密码太短console 里save!会直接报Password is too short或者数据库直改时虽然写进去了但应用校验时拒绝登录。建议新密码至少 12 位大小写字母加数字加特殊符号都来一点既满足策略也降低被爆破的风险。第二个是 Rails 环境不对。console 进入后先执行Rails.env如果输出是development你改的就是开发环境的库生产环境数据完全没动改了个寂寞。所以前面才强调进入 console 时最好显式带production参数。第三个是缓存混淆。有些时候密码明明改成功了网页端用旧密码还能登录看起来像是缓存在作怪。实际上 GitLab 的密码校验直接查数据库不会缓存密码本身。真正的问题可能是你在浏览器里保存了旧的会话页面显示的是登录成功后的缓存视图并不是真的登录了。这时候无痕窗口试一次最干净。第四个是日志提示。如果登录还是失败去翻日志看具体报了什么错sudo tail -f /var/log/gitlab/gitlab-rails/production_json.logDocker 容器内对应路径相同只是先进入容器再查看。日志里通常会写明具体是密码错误、账户被锁、OTP 缺失还是 LDAP 阻断比盲猜高效得多。5. 重置完成后的收尾工作从单点故障到多管理员体系5.1 把root从唯一钥匙变成多把钥匙密码找回来之后第一件事不是欢呼而是立刻在密码管理器里记录新密码。我个人的习惯是设置完密码的 30 秒内就存入 Bitwarden 或 1Password并且确认密码强度足够不再依赖脑子记这种方式。GitLab 的 root 密码一旦再次丢失又要重复一遍上面的流程折腾一次就够了。更重要的一步是创建第二个管理员账号。很多团队整个 GitLab 就靠 root 一个人撑着一旦 root 密码丢失、手机号变更导致 2FA 失联平台一下子就失控了。正确做法是在 Admin Area 里新建一个普通用户然后把它提升为管理员由不同的人分别保管 root 和这个备用管理员的密码。这样哪怕 root 一时想不起来也能通过备用管理员从后台重置不至于被卡在门外。5.2 CI/CD里的root token改密码后别忘了同步换掉如果你用过 root 的 Personal Access Token 来配置 Jenkins、GitLab Runner 或者 CI/CD 流水线这里有个容易被忽略的点root 密码被重置已经签发的 Access Token 不会因为密码变化而失效。也就是说你改完密码CI/CD 里的 token 照样能用但这也意味着如果之前 token 已经泄露你这次重置密码并没有把它清理掉。所以建议在本次重置完成的同时进入 Admin Area找到 root 用户的 Access Tokens 列表把里面不是当前在用的 token 全部 revoke然后按需重新签发。特别是 Jenkins 里配置 GitLab connection 用的 token重新签发后记得同步去 Jenkins 更新否则下一次构建会突然失败。这里也顺便提醒一句CI/CD 流水线里尽量别用 root 级别的 token。它的权限范围太大一旦泄露等于整个代码平台裸奔。我现在的做法是单独创建一个只读或只对特定项目有权限的机器人账号把 token 的 scope 压到最小代码平台的整体安全等级会高很多。5.3 顺手把external_url和clone地址理顺密码能进后台之后顺手检查一下 GitLab 的external_url配置。很多人用机器 IP 或容器 ID 访问 GitLabclone 地址看起来就是一长串莫名其妙的字符比如http://gitlab.example.com/root/project.git和http://172.17.0.2/root/project.git完全是两个体验。在/etc/gitlab/gitlab.rb里把external_url配置成团队约定的域名external_url http://gitlab.example.com然后执行sudo gitlab-ctl reconfigure这样重新配置后Web 端的 clone 地址、CI/CD 变量里的仓库地址都会以新域名为准。这个操作虽小但能省去团队里一堆日常困惑。5.4 密码定期轮换与备份演练这次经历之后我给自己的运维检查清单里加了两条硬性要求。第一root 密码每季度轮换一次轮换后同步更新密码管理器记录同时检查备用管理员账号是否可正常登录。这个周期可以根据团队规模调整但从不轮换是最差的做法。第二定期做一次 GitLab 备份恢复演练。备份文件里包含users表也就包含了密码哈希。虽然哈希是密文但如果备份文件泄露攻击者依然可以拿哈希做离线爆破。备份的存放权限要收紧加密备份最好。更重要的是每隔几个月尝试从备份恢复一次到临时环境验证备份本身就是可用的否则真到灾难恢复时才发现备份是坏的才是真正的绝望。把这两条加到日常运维动作里之后我面对忘记 root 密码这种事的心理负担小了很多。工具总有意外但流程可以兜底。最后再分享一个小技巧我现在每次重置完 GitLab 管理员密码都会顺手在 console 里把failed_attempts和otp_required_for_login一起清掉绝不只改密码就收工。这看起来是随手一步但能省掉后面至少两轮登录失败的排查时间。如果你正被这个问题卡住按上面第一到第四章的顺序走一遍大概率能解决要是卡在某个具体报错上先去翻 production 日志通常答案就在里面。
返回列表