ملاحظة5 دقائق قراءة

استخدم SSH بدل HTTPS مع Git

أوقفت GitHub قبول كلمات المرور مع Git عبر HTTPS في آب 2021. و‏credential helper يريحك من الكتابة لا من انتهاء الصلاحية؛ أما المفتاح فيريحك منهما معًا، ويطال روابط HTTPS التي لم تكتبها أنت.

قبل عشرة أيام أوقفت GitHub قبول كلمات مرور الحسابات مع Git عبر HTTPS. فإن كنت تدفع تعديلاتك عبر HTTPS صرتَ اليوم تلصق personal access token بدل كلمة المرور: سرٌّ أطول، محفوظٌ حيث كانت كلمة المرور، تنتهي صلاحيته في موعدٍ عليك أن تتذكّره.

ويكفيك credential helper عناءَ الكتابة — ‏osxkeychain أو Git Credential Manager أو gh auth login — وإن كان الـ‏token يناسبك فهو جوابٌ وجيه. لكنّ ما لا يكفيك إياه انتهاءُ الصلاحية: فكل بضعة أشهر تنتهي صلاحيته، فتعود إلى صفحة الإعدادات تولّد غيره وتحاول أن تتذكّر أيّ scopes كانت له.

أما مفتاح SSH فلا صلاحية له تنتهي. تُولّد زوجًا مرة واحدة، وتُبقي النصف الخاص على جهازك، وتُسلّم النصف العام إلى الخادم، ويبقى الأمر قائمًا حتى تُلغيه أنت. وهو يبلغ أيضًا ما لا يبلغه الـ‏helper: روابط HTTPS التي لم تكتبها أنت، في مستودعاتٍ وملفات تبعيّاتٍ يملكها غيرك.

والخطوتان الأوليان تجدهما في كل شرح. أما الخطوتان بعدهما فهما ما يجعل هذا الإعداد يثبت.

توليد المفتاح

ssh-keygen -t ed25519 -C "personal github" -f ~/.ssh/personal-github

‏Ed25519 بدل RSA: مفاتيح أقصر، ومصافحةٌ أسرع، ولا وسيطَ لحجم المفتاح تخطئ فيه. وإن كان ما تتعامل معه أقدم من OpenSSH 6.5 فالبديل -t rsa -b 4096.

سمِّ المفتاح باسم ما يفتحه — personal-github أو work-gitlab — وشغّل ssh-keygen مرةً لكل حساب. فـ‏GitHub ترفض مفتاحًا عامًّا مسجَّلًا في حسابٍ آخر، وحسابان يفرضان مفتاحين شئتَ أم أبيت. وولّد على كل جهازٍ مفتاحه، ولا تنقل نصفًا خاصًّا من جهازٍ إلى جهاز: فمفتاحٌ يوجد في مكانٍ واحد مفتاحٌ تُلغيه من مكانٍ واحد. أما id_ed25519_2 فلن يخبرك بشيء يوم تحتاج إلى إلغائه.

واستعمل عبارة مرور؛ فهي مع ssh-agent تكلّفك طلبًا واحدًا في الجلسة، لا طلبًا مع كل دفعة.

إضافة المفتاح العام إلى الخادم

انسخ النصف العام، أي الملف المنتهي بـ .pub. أما الذي بلا امتدادٍ فيبقى حيث وُلِّد:

# macOS
pbcopy < ~/.ssh/personal-github.pub

# Linux
xclip -selection clipboard < ~/.ssh/personal-github.pub

ثم ألصقه في إعدادات حسابك: GitHub أو GitLab أو Bitbucket.

إخبار SSH بأيّ مفتاح يستعمل

يجرّب SSH إن تُرك وشأنه الأسماءَ الافتراضية — id_ed25519 و‏id_rsa — فلا يُعرَض مفتاحٌ اسمه personal-github أصلًا. في ملف ~/.ssh/config:

Host github.com
  User git
  IdentityFile ~/.ssh/personal-github
  IdentitiesOnly yes
  AddKeysToAgent yes

Host gitlab.com
  User git
  IdentityFile ~/.ssh/work-gitlab
  IdentitiesOnly yes
  AddKeysToAgent yes

و‏IdentitiesOnly yes سطرٌ لا غنى عنه. فبدونه يعرض SSH كل مفتاحٍ لدى ssh-agent، وتوثّقك GitHub بالحساب الذي يطابق أولَ مفتاحٍ ينطبق — فيعود مستودعٌ تملك صلاحية الوصول إليه بجواب «غير موجود». يبدو ذلك خللًا في الصلاحيات وليس كذلك.

وعلى macOS يحفظ UseKeychain yes في كل كتلة عبارةَ المرور في Keychain فتبقى بعد إعادة التشغيل. غير أنّ الخيار من نسخة آبل من OpenSSH لا من الأصل، فإن كانت نسخة Homebrew من openssh أسبقَ في PATH رفض الإقلاع. وحمّل المفتاح إلى ‏Keychain مرةً واحدة بـ ssh-add -K ~/.ssh/personal-github.

واتّصل مرةً واحدة، لتتحقّق من المفتاح ولتجيب عن السؤال الذي يطرحه SSH أولَ مرةٍ يلتقي فيها خادمًا:

ssh -T git@github.com

يعرض السؤال بصمة مفتاح الخادم. قارنها بالبصمات التي تنشرها GitHub قبل أن تجيب بـ yes: فهذا السؤال هو كلّ ما يفعله SSH من توثيقٍ للخادم، وهو أسهل ما في هذه الصفحة أن تجيب عنه بلا تفكير. وبعده تعني تحيةٌ باسم مستخدمك أن المفتاح يعمل.

وإن تعلّق الاتصال بدل ذلك فالأرجح أن الشبكة تحجب المنفذ 22، و‏GitHub تستقبل SSH على المنفذ 443 أيضًا:

Host github.com
  Hostname ssh.github.com
  Port 443
  # ...the rest of the block as above

والأولى أن تضبطه قبل أن تحتاجه، فبعد الخطوة التالية لن يبقى HTTPS متاحًا مخرجًا احتياطيًا.

إعادة كتابة روابط HTTPS إلى SSH

لا ينفع المفتاح ما دامت عناوين مستودعاتك البعيدة تبدأ بـ https://. وبدل إصلاحها مستودعًا مستودعًا، اطلب من Git أن يستبدلها، في ملف ~/.gitconfig:

[url "ssh://git@github.com/"]
  insteadOf = https://github.com/

[url "ssh://git@gitlab.com/"]
  insteadOf = https://gitlab.com/

[url "ssh://git@bitbucket.org/"]
  insteadOf = https://bitbucket.org/

الآن يُعاد كتابة كل رابط HTTPS لتلك المواقع قبل أن يغادر الطلبُ جهازك، بما في ذلك الروابط التي لم تكتبها أنت. و‏submodules وحدها تبرّر هذا الإعداد: ملف .gitmodules أودعه غيرك، يشير إلى HTTPS، في مستودعٍ لا رأي لك فيه. ومثله كل تبعيّةٍ تسمّي مستودع Git برابط HTTPS — حزمة pod فيها سطر :git =>، أو go get تحت GOPRIVATE، أو حزمة npm مكتوبة git+https://.

وهناك أمران لا يشملهما هذا الإعداد، وهذا هو الصحيح. فـ‏package managers التي تجلب أرشيفًا بدل أن تستنسخ لا تسلّم Git رابطًا أصلًا: انتقلت CocoaPods إلى CDN في الإصدار 1.8، و‏Go proxy يجلب الوحدات العامة، واختصار npm بصيغة github:user/repo ينتهي إلى أرشيفٍ من codeload. وهذا الملف ~/.gitconfig على جهازك أنت، و‏CI runner له مستخدمه ومجلده الخاص ولن يراه أبدًا. وهذا هو المطلوب: فالمفروض أن يوثّق CI نفسه بـ‏token محدود الصلاحيات عبر HTTPS، لا بمفتاحك الشخصي.

التحقق من إعادة الكتابة

يجري الاستبدال داخل Git قبل الشبكة، فيمكنك أن تراه دون أن تتصل بشيء:

git ls-remote --get-url https://github.com/omaralbeik/SwifterSwift.git

فإن طبع ssh://git@github.com/omaralbeik/SwifterSwift.git فالاستبدال يعمل. والجأ إلى هذا بدل استنساخ مستودعٍ عام للاختبار: فاستنساخ المستودعات العامة عبر HTTPS ينجح بلا بيانات اعتماد في الحالين، فلا يدلّك نجاحه على شيء.

وهذا هو الترتيب كلّه: Git يعيد كتابة الرابط، و‏SSH يختار المفتاح الذي يخصّ الخادم. لا صلاحية تنتهي، ولا شيء تلصقه.

المواضيع