SSH, mode pengguna, TCP / IP dan WireGuard

Siapapun yang menginangi aplikasi dari penyedia seperti Fly.io (selanjutnya disebut Fly) mungkin perlu terhubung ke server yang menjalankan aplikasi ini melalui SSH.



Tapi Fly adalah semacam kambing hitam di antara platform serupa lainnya. Perangkat keras kami berfungsi di pusat data yang tersebar di seluruh dunia. Server kami terhubung ke Internet melalui jaringan Anycast, dan mereka terhubung satu sama lain menggunakan jaringan WireGuard. Kami mengambil kontainer Docker dari pengguna dan mengubahnya menjadi mikrovirtual Firecracker. Dan ketika kami pertama kali memulai, kami melakukannya untuk memberi pelanggan kami kemampuan untuk menjalankan "aplikasi edge". Aplikasi ini biasanya relatif kecil, potongan kode mandiri yang sangat sensitif terhadap kinerja jaringan. Akibatnya, cuplikan kode ini harus dijalankan di server yang terletak sedekat mungkin dengan pengguna. Dalam lingkungan seperti itu, kemampuan untuk terhubung ke server melalui SSH tidak terlalu penting.







Tapi sekarang tidak semua klien kami menggunakan Fly dengan cara ini. Saat ini, di lingkungan Fly, Anda dapat dengan mudah mengeksekusi semua kode yang terkait dengan aplikasi. Kami telah menyederhanakan prosedur untuk memulai ansambel layanan di lingkungan berkerumun. Layanan semacam itu dapat, menggunakan saluran komunikasi yang aman, berinteraksi satu sama lain, mereka dapat menyimpan data secara permanen, mereka dapat, melalui jaringan WireGuard, berkomunikasi dengan operator mereka. Jika saya melanjutkan cerita tentang sistem kami dengan semangat yang sama, maka saya harus memberikan tautan ke semua materi yang telah kami tulis selama beberapa bulan terakhir.



Namun, bagaimanapun, kami tidak memiliki dukungan SSH normal.



Jelas, tentu saja, Anda dapat dengan mudah membuat container dengan layanan SSH yang dapat Anda sambungkan melalui SSH. Platform terbang mendukung pekerjaan dengan port TCP umum (dan juga port UDP ). Jika klien, menggunakan file tersebut fly.toml



, "memberi tahu" jaringan Anycast kami tentang port SSH anehnya, sistem akan mengatur perutean koneksi SSH-nya, setelah itu semuanya akan berfungsi sebagaimana mestinya.



Tetapi mereka yang membuat container biasanya tidak melakukan ini, dan kami tidak menyarankan mereka melakukannya. Hasilnya, kami melengkapi Fly dengan dukungan SSH. Apa yang telah kami lakukan diatur dengan cara yang agak tidak biasa. Pada artikel ini, yang terdiri dari dua bagian, saya akan membahasnya.



Bagian 1: 6PN dan Hallpass



Saya banyak menulistentang bagaimana jaringan pribadi diatur di Fly. Singkatnya, ternyata apa yang kami miliki dapat dibandingkan dengan versi IPv6 yang disederhanakan dari "virtual private cloud" GCP atau AWS. Kami menyebut sistem ini 6PN. Ketika sebuah instance aplikasi (mesin mikrovirtual Firecracker) diluncurkan di Fly, kami menetapkan awalan IPv6 khusus untuk instance ini. Beberapa pengenal dikodekan di awalan: pengenal aplikasi, organisasi yang memiliki aplikasi, dan sumber daya perangkat keras tempat aplikasi dijalankan. Kami menggunakan sedikit kode eBPF untuk secara statis merutekan paket IPv6 tersebut di jaringan WireGuard internal kami dan untuk memastikan bahwa klien tidak dapat terhubung ke sistem organisasi tempat mereka tidak terlibat.



Anda juga dapat menggunakan WireGuard untuk menjembatani jaringan IPv6 pribadi yang kami buat dengan jaringan lain. API kami dapat membuat konfigurasi WireGuard yang dapat digunakan, misalnya, pada host EC2 untuk proxy RDS Postgres . Atau, jika perlu, Anda dapat menggunakan klien WireGuard (di Windows, Linux, atau macOS) untuk menyambungkan komputer pengembangan ke jaringan pribadi Anda.



Anda mungkin sudah tahu apa yang saya maksud.



Kami menulis server SSH yang sangat kecil dan sangat sederhana di Go bernama Hallpass. Ini dapat dibandingkan dengan "Hello, World!" Dibuat menggunakan perpustakaan Go x/crypto/ssh



... (Jika saya melakukan ini lagi, saya mungkin hanya akan menggunakan paket Glider Labs untuk membangun server SSH. Dengan menggunakan paket ini, server kami secara harfiah akan menjadi "Halo, Dunia!" Inisialisasi semua contoh mesin mikrovirtual Firecracker dilakukan dan Hallpass adalah diluncurkan dengan mengikat ke alamat 6PN mereka.



Jika Anda dapat beroperasi di jaringan 6PN organisasi Anda (misalnya, melalui koneksi WireGuard), itu berarti Anda dapat masuk ke instans mikrovirtual menggunakan Hallpass.



Hanya ada satu detail menarik tentang cara kerja Hallpass. Ini tentang otentikasi. Elemen infrastruktur di jaringan produksi kami biasanya tidak memiliki akses langsung ke API kami atau ke database yang mendasarinya. Dan contoh Petasan itu sendiri, tentu saja, juga tidak memiliki akses ini. Ini menyebabkan beberapa kesulitan terkait dengan mengubah pengaturan komunikasi. Misalnya, bagaimana Anda dapat menjawab pertanyaan tentang jenis kunci yang Anda perlukan untuk terhubung ke mesin mikrovirtual tertentu?



Kami menemukan solusi untuk masalah ini dengan menggunakan sertifikat klien SSH. Alih-alih harus berurusan dengan penyerahan kunci setiap kali pengguna ingin masuk dari host baru, kami membuat sertifikat root untuk mengatur pengguna tersebut. Kunci publik untuk sertifikat root ini dihosting di sistem DNS pribadi kami, dan Hallpass menghubungi DNS untuk mendapatkan sertifikat ini setiap kali login dilakukan. API kami menandatangani sertifikat baru untuk pengguna, sertifikat ini dapat digunakan untuk masuk ke sistem.



Anda mungkin memiliki pertanyaan tentang solusi ini. Oleh karena itu, saya akan mengungkapkan beberapa detail lebih lanjut tentang dia.



Pertama, mari kita bicara tentang sertifikat. Dekade X.509 Madnessโ€Mungkin menyebabkan kataโ€œ sertifikat โ€memberi Anda rasa yang tidak menyenangkan. Dan saya tidak menyalahkan Anda untuk itu. Tetapi sertifikat harus digunakan saat mengatur koneksi SSH, karena sertifikat semacam itu dalam kasus ini adalah solusi yang baik. Namun, sertifikat SSH bukanlah sertifikat X.509. Ia menggunakan format OpenSSH -nya sendiri , dan, secara umum, tidak ada yang istimewa yang dapat dikatakan tentang sertifikat ini. Mereka, seperti semua sertifikat lainnya, memiliki "tanggal kedaluwarsa", yang memungkinkan Anda membuat kunci berumur pendek (dan ini, hampir selalu, persis seperti yang Anda butuhkan). Dan, tentu saja, mereka memungkinkan Anda untuk menetapkan satu kunci publik ke seluruh grup server, yang dapat mengotorisasi sejumlah kunci privat. Tidak perlu terus-menerus memperbarui server yang sesuai.



Berikutnya adalah API dan penandatanganan sertifikat kami. Baik! Kami sangat berhati-hati, tetapi sertifikat ini umumnya seaman token akses Fly. Saat ini, sertifikat tidak dapat dilindungi dengan lebih baik daripada token, karena token memungkinkan penerapan versi baru wadah aplikasi. Bekerja dengan Web PKI X.509 CA melibatkan banyak formalitas. Kami melakukannya tanpa mereka.



Dan terakhir, DNS kami. Dia, saya setuju, terlihat seperti omong kosong. Tapi sebenarnya tidak seburuk itu. Setiap host yang menjalankan contoh mikrovirtual Firecracker menjalankan versi lokal dari server DNS pribadi kami (program kecil yang ditulis dengan Rust). Kode eBPF memastikan bahwa mesin Petasan hanya dapat berinteraksi dengan server DNS ini, merujuknya dari alamat 6PN server mereka. (Dari sudut pandang teknis, pengguna hanya dapat membuat kueri ke API DNS pribadi server ini, dan semua kueri pengguna lain akan diproses secara rekursif.) Server DNS dapat (saya tahu ini terlihat tidak biasa) dengan andal mengidentifikasi organisasi dengan menganalisis permintaan alamat IP sumber. Secara umum, begitulah cara kami bekerja.



Semua ini terjadi di dalam sistem kami, pengguna tidak dapat melihat semua ini. Pengguna hanya melihat perintah flyctl ssh issue -a



yang meminta sertifikat baru dari API kami, dan kemudian meneruskannya ke agen SSH lokal, setelah itu koneksi SSH, secara umum, ternyata dapat beroperasi. Semua ini sudah diatur dengan cukup rapi. Tetapi bisnis apa pun selalu dapat dilakukan dengan lebih akurat dari sebelumnya.



Bagian 2: Bekerja pada jaringan WireGuard dari mode pengguna menggunakan TCP / IP



Ada satu masalah dengan skema penggunaan SSH di atas, yaitu tidak semua orang memasang WireGuard. Program yang sesuai, bagaimanapun, harus dipasang oleh semua orang. WireGuard adalah teknologi hebat yang banyak membantu untuk mengelola aplikasi yang berjalan pada platform Fly. Namun, bagaimanapun juga, beberapa pengguna kami tidak memiliki WireGuard.



Benar, pengguna seperti itu juga perlu bekerja dengan sistem mereka melalui SSH.



Pada pandangan pertama, fakta bahwa seseorang tidak memasang WireGuard tampak seperti rintangan yang tidak dapat diatasi. Bagaimana cara kerja WireGuard? Antarmuka jaringan baru dibuat di komputer pengguna. Ini adalah antarmuka WireGuard tingkat kernel (di Linux), atau terowongan dengan layanan WireGuard mode pengguna yang terpasang padanya (di semua sistem operasi lain). Tanpa antarmuka jaringan ini, Anda tidak dapat bekerja dengan jaringan WireGuard.



Tetapi jika Anda melihat WireGuard dari sudut yang tepat, Anda dapat melihat bahwa, dari sudut pandang teknis, ini bukanlah masalahnya. Yaitu, hak istimewa tingkat sistem operasi diperlukan untuk mengonfigurasi antarmuka jaringan baru. Tetapi untuk mengirim paket ke 51820/udp



tidak ada hak istimewa yang dibutuhkan. Apa pun yang diperlukan untuk membuat protokol WireGuard berfungsi dapat dimulai sebagai proses tanpa hak yang berjalan dalam mode pengguna. Beginilah cara kerja paket wireguard-go .



Ini hanya memungkinkan Anda melalui prosedur jabat tangan WireGuard. Tetapi pada saat yang sama, kami tidak berbicara tentang pertukaran informasi dengan node jaringan WireGuard, karena Anda tidak bisa begitu saja mengambil dan mengirim beberapa data sewenang-wenang ke sistem lain yang terhubung ke jaringan ini. Sistem seperti itu mendengarkan paket yang biasanya akan dikirim melalui jaringan TCP / IP. Alat sistem standar yang mendukung soket UDP tidak membantu dalam membuat koneksi TCP menggunakan soket tersebut.



Apakah sulit untuk menulis sepotong kecil kode yang mengaktifkan TCP dalam mode pengguna, yang hanya dirancang untuk mendukung komunikasi melalui jaringan WireGuard, lagi-lagi dalam mode pengguna? Kode semacam itu akan memungkinkan pengguna Fly untuk terhubung ke sistem mereka melalui SSH tanpa harus menginstal perangkat lunak yang mendukung WireGuard.



Saya ceroboh dalam mendiskusikan semua ini di saluran Slack tempat Jason Donenfeld berada. Yakni, setelah berpikir keras, saya pergi tidur. Ketika saya bangun, Jason telah menerapkan semua ini menggunakan gVisor dan termasuk dalam pustaka WireGuard.



Yang paling menarik di sini adalah gVisor. Kami sudah menulis tentang itu ... Jika ada yang tidak tahu, gVisor pada dasarnya adalah OS Linux ruang pengguna, Linux diimplementasikan di Golang, digunakan sebagai pengganti runc



untuk menjalankan container. Ini sebenarnya adalah proyek yang benar-benar gila. Dan jika Anda menggunakannya, saya kira Anda dapat dengan bangga memberi tahu orang lain tentangnya, karena itu hanya hal yang indah. Di kedalamannya terdapat implementasi TCP / IP lengkap, ditulis dalam Go, yang beroperasi pada data input dan output yang direpresentasikan sebagai buffer biasa []byte



.



Kemudian beberapa tweet di-tweet, dan beberapa jam kemudian saya mendapat email yang sangat bagus dari Ben Barkert... Ben sudah mengerjakan berbagai tugas yang berkaitan dengan subsistem jaringan gVisor, dia tertarik dengan apa yang sedang kami kerjakan, dia ingin tahu apakah kami ingin bekerja sama dengannya. Kami menyukai idenya untuk bekerja sama dalam proyek ini. Dan sekarang, tanpa membahas detailnya, kami memiliki implementasi SSH berbasis sertifikat yang dijalankan melalui implementasi TCP / IP gVisor mode pengguna. Ini semua berinteraksi dengan jaringan WireGuard melalui paket mode kustom wireguard-go



. Dan akhirnya, benda ini dibangun ke dalam flyctl



.



Untuk menggunakan SSH menggunakan flyctl



- cukup masukkan perintah seperti ini:



flyctl ssh shell personal dogmatic-potato-342.internal

      
      





Dan sekarang, agar Anda dapat menyadari ketidakpercayaan tentang apa yang terjadi, saya akan memberi tahu Anda sedikit tentang perintah ini. Jadi - dogmatic-potato-342.internal



adalah nama DNS internal yang hanya diselesaikan oleh server DNS pribadi di jaringan 6PN. Semua ini efisien karena fakta bahwa dalam mode tersebut ssh shell



utilitas flyctl



menggunakan mode pengguna gVisor stack TCP / IP. Tetapi tidak ada kode di gVisor untuk melakukan pencarian DNS. Ini hanyalah pustaka Go standar yang kami tipu dengan memasukkan antarmuka TCP / IP khusus kami ke dalamnya.



Flyctl



, omong-omong, ini adalah proyek sumber terbuka(Seharusnya demikian, karena klien perlu menggunakannya di komputer mereka sendiri tempat mereka terlibat dalam pengembangan). Oleh karena itu, jika Anda tertarik, Anda tinggal membaca kodenya. Ben menulis beberapa kode bagus di folder pkg . Dan sisa kodenya, mengerikan, tulisku. Di Go, menyediakan komunikasi IP di jaringan WireGuard ternyata sangat sederhana. Jika Anda pernah melakukan pemrograman TCP / IP tingkat rendah, Anda mungkin menganggap kesederhanaan ini luar biasa. Objek dari tumpukan TCP gVisor terhubung langsung ke kode jaringan pustaka standar.



Lihatlah kode ini:



tunDev, gNet, err := netstack.CreateNetTUN(localIPs, []net.IP{dnsIP}, mtu)
if err != nil {
    return nil, err
}

// ...

wgDev := device.NewDevice(tunDev, device.NewLogger(cfg.LogLevel, "(fly-ssh) "))

      
      





CreateNetTUN



Merupakan bagian wireguard-go



. Di sinilah kapabilitas gVisor digunakan. Pertama-tama, kami memiliki perangkat terowongan sintetis yang dapat digunakan untuk membaca dan menulis paket biasa yang menyediakan operasi WireGuard. Kedua, kami memiliki fungsi net.Dialer , pembungkus untuk gVisor, yang dapat digunakan dalam kode Go dan melaluinya berinteraksi dengan jaringan WireGuard yang sesuai.



Semuanya? Secara umum, ya. Misalnya, berikut cara kami menggunakan mekanisme ini untuk bekerja dengan DNS:



resolv: &net.Resolver{
    PreferGo: true,
    Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
        return gNet.DialContext(ctx, network, net.JoinHostPort(dnsIP.String(), "53"))
    },
},

      
      





Ini adalah kode jaringan normal yang ditulis di Go. Secara umum, hasilnya bagus.



Jelas setiap orang harus melakukan ini.



Berkat beberapa ratus baris kode (ini - terlepas dari kode implementasi mode pengguna Linux yang kami dapatkan dari gVisor; tetapi apa yang harus dilakukan - tidak ada jalan keluar dari ketergantungan), Anda bisa mendapatkan jaringan baru dengan kriptografi otentikasi yang Anda inginkan. Jaringan yang dapat diakses kapan saja dan dari hampir semua program.



Jelas bahwa jaringan seperti itu secara signifikan lebih lambat daripada yang didasarkan pada implementasi inti TCP / IP. Tetapi apakah itu sering kali sangat penting? Dan, secara khusus, apakah itu sering memiliki arti ketika memecahkan masalah yang muncul secara berkala, untuk solusi yang aneh, tidak diketahui dari apa, terowongan TLS biasanya dibangun? Saat kecepatan penting, Anda cukup beralih ke implementasi WireGuard biasa.



Bagaimanapun, apa yang saya katakan memecahkan masalah besar kami. Bagaimanapun, sistem ini cocok tidak hanya untuk mengatur pekerjaan SSH. Kami juga menghosting database Postgres. Sangat mudah bila memungkinkan, dengan menjalankan perintah sederhana, untuk benar-benar membuka shell dari mana saja psql



, terlepas dari apakah mungkin, pada waktu yang tepat, untuk menginstal WireGuard untuk macOS.



Apakah Anda menggunakan WireGuard?






All Articles