Go тілі: нөлден өз блогыңызға дейін 47-сабақ (барлығы 50)
Өз доменің және HTTPS: блогты атпен ашсын
Go курсының қырық жетінші сабағы. Домен — сіздің мекенжайыңызға апаратын DNS-тегі бір жазба, ал HTTPS — құсбелгі емес, қажеттілік: ашық арнада оқырманның құпиясөзі толық көрінетіні өлшенді, барлық 222 байт, ал TLS-те ол байттарда жоқ. Қоса алдыңғы прокси әрі сенуге болмайтын тақырып.
Не үшін керек
Блог 95.217.3.14:10000 тәрізді мекенжайда тұр. Мұндайды досқа жібермейді әрі визиткаға жазбайды — оны жай ғана ұмытады. Ат керек: blog.example.kz.
Әрі атпен бірге мекенжай жолағында құлып керек. Әдемілік үшін емес: онсыз оқырманның формаға енгізгенінің бәрі бөтен Wi-Fi арқылы ашық мәтінмен ұшады. Қаншалықты ашық екенін қазір көреміз.
Домен әлі болмауы мүмкін, әрі бұл сабақты өткізіп жіберуге себеп емес. Бірінші жартысы — ашық арнада не көрінетіні және TLS не беретіні — өз машинаңызда, бір тиын жұмсамай өлшенеді. Екіншісі — DNS жазбасы, сертификат және прокси — домен пайда болған күнге арналған жоспар ретінде оқылады. Курстағы блог доменсіз де аяқталған болып қалады: ол мекенжай мен порт бойынша жұмыс істейді.
Бірден тұтас
Жаңа қалта, go mod init sabaq44. Бағдарлама оқырман мен блогтың арасындағы бөтен Wi-Fi-ды бейнелейді: ол байттарды әрі қарай өткізеді әрі не өткенін есінде сақтайды.
package main
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strings"
)
// Оқырман кіру формасына енгізетін құпиясөз. Әрі қарай оқырман мен блогтың
// арасындағы бөтен Wi-Fi нені көретінін қараймыз.
const password = "degentaudynbasy"
func main() {
// Кіру формасын қабылдайтын блог. Бір өңдеуші TLS-сіз де, TLS-пен де
// жұмыс істейді: айырмашылық кодта емес, арнада.
login := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintf(w, "кірді: %s", r.FormValue("email"))
})
fmt.Println("== http: сіз бен сайттың арасында бөтен отыр")
plain := httptest.NewServer(login)
defer plain.Close()
seen := post(plain.URL, false)
show(seen)
fmt.Println()
fmt.Println("== https: сол өңдеуші, сол құпиясөз")
secure := httptest.NewTLSServer(login)
defer secure.Close()
seen = post(secure.URL, true)
show(seen)
}
// post форманы тыңдап отырған прокси арқылы жібереді әрі сол прокси жолда
// оқып үлгергеннің бәрін қайтарады.
func post(target string, useTLS bool) *bytes.Buffer {
u, _ := url.Parse(target)
addr, seen := spy(u.Host)
client := &http.Client{Transport: &http.Transport{
// Куәлік өзі қол қойылған, сондықтан тексеруді өшіреміз: сабақта бізді
// сенім емес, арнада не көрінетіні қызықтырады.
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
scheme := "http"
if useTLS {
scheme = "https"
}
resp, err := client.PostForm(scheme+"://"+addr+"/login", url.Values{
"email": {"oqyrman@example.kz"},
"password": {password},
})
if err != nil {
fmt.Println("сұраныс өтпеді:", err)
return seen
}
resp.Body.Close()
return seen
}
// spy — бөтен Wi-Fi: ол байттарды әрі қарай жібере береді, бірақ оқырманнан
// сайтқа өткеннің бәрін есінде сақтайды.
func spy(target string) (string, *bytes.Buffer) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(err)
}
seen := &bytes.Buffer{}
go func() {
conn, err := l.Accept()
if err != nil {
return
}
up, err := net.Dial("tcp", target)
if err != nil {
return
}
go io.Copy(conn, up)
io.Copy(io.MultiWriter(up, seen), conn)
}()
return l.Addr().String(), seen
}
// show көргенінің бірінші жолын басып шығарады әрі жалғыз маңызды сұраққа
// жауап береді: осы байттарда құпиясөз көріне ме.
func show(seen *bytes.Buffer) {
raw := seen.String()
first := strings.SplitN(raw, "\r\n", 2)[0]
if len(first) > 60 {
first = first[:60]
}
if !isText(first) {
first = fmt.Sprintf("% x …", seen.Bytes()[:min(12, seen.Len())])
}
fmt.Printf("өткен байт: %d\n", seen.Len())
fmt.Printf("басы: %s\n", first)
if strings.Contains(raw, password) {
fmt.Printf("арнадағы құпиясөз: толық көрінеді — %q\n", password)
return
}
fmt.Println("арнадағы құпиясөз: табылмады")
}
// isText — мұны адамға жол ретінде көрсетуге бола ма.
func isText(s string) bool {
for _, r := range s {
if r < 32 && r != '\t' {
return false
}
}
return true
}
Шығатыны:
== http: сіз бен сайттың арасында бөтен отыр
өткен байт: 222
басы: POST /login HTTP/1.1
арнадағы құпиясөз: толық көрінеді — "degentaudynbasy"
== https: сол өңдеуші, сол құпиясөз
өткен байт: 1761
басы: 16 03 01 05 a8 01 00 05 a4 03 03 88 …
арнадағы құпиясөз: табылмады
Талдау
Ашық арнадағы құпиясөз — жай ғана сөз
Шығыстың бірінші блогын түсіндірудің қажеті жоқ: бөтен қатынау нүктесі арқылы өткен 222 байтта құпиясөз толық, әріптермен жатыр. «Теориялық осал» емес, көрініп тұр.
Екінші блокта сол өңдеуші әрі сол құпиясөз, бірақ арна басқа: 16 03 01-ден басталатын 1761 байт — бұл TLS жазбасының тақырыбы, — әрі оларда құпиясөз жоқ.
Айырмашылық блогтың кодында емес. Өңдеушіні тіпті өзгерткен жоқпыз: ол екі жағдайда да бір. Айырмашылық сұраныстың қай арнамен келетінінде.
Елестетіп көріңіз. Ашық хат пен конверт. Пошташы қаскөй емес, бірақ ашық хатты ол жүре салып оқиды, ал конвертті — жоқ.
Домен — DNS-тегі бір жазба
Доменді «сайтқа тіркемейді». Домен машинаның мекенжайын A типті бір жазбамен көрсетеді, әрі бұл былай көрінеді:
== домен қайда сілтейді
shanraq.org. 231 IN A 85.202.192.61
== http не жауап береді
HTTP/1.1 308 Permanent Redirect
Connection: close
Location: https://shanraq.org/
Server: Caddy
== сертификат кімдікі және қай күнге дейін
issuer=C=US, O=Let's Encrypt, CN=YE1
subject=CN=shanraq.org
notBefore=Jul 27 04:47:02 2026 GMT
notAfter=Oct 25 04:47:01 2026 GMT
== сайт нені есте сақтауды сұрайды
HTTP/2 200
strict-transport-security: max-age=63072000; includeSubDomains
Бірінші жол — бүкіл құпия: shanraq.org дегеніміз 85.202.192.61, ал қасындағы 231 саны — браузерге жауапты қайта сұрамай, қанша секунд есте сақтауға рұқсат. Осыдан ереже шығады: мекенжайды алдын ала өзгертіңіз. Осы санды бір минутқа дейін түсірдіңіз, күттіңіз, көштіңіз — сонда көшу тәулік емес, минут алады.
Домен әлі жоқ болса не істеу керек: оны тіркеушіден сатып алу (.kz үшін жергілікті тіркеушілер, .com пен .org үшін кез келгені), домен панелінде өз IP-іңізбен A жазбасын жасау әрі күту. Болды, ешқандай сиқыр жоқ.
HTTPS-ті хостинг емес, куәландыру орталығы береді
Куәлік — доменнің сіздікі екенін растайтын, браузерлер сенетін тараптың қолы қойылған құжат. Бұрын оны сатып алатын; қазір Let’s Encrypt оны тегін әрі автоматты береді, әрі біздің сайтымыз дәл солай тұр — өлшемдердің үшінші блогында оның куәлігі.
Осы блокта үш нәрсені оқыған жөн. issuer — кім берді, subject — кімге, ал күндер куәліктің үш ай тұратынын айтады. Бұл сараңдық емес: қысқа мерзім жаңартуды автоматты болуға мәжбүрлейді, ал автоматты жаңартуды ұмытпайды.
Алдыңғы прокси: кілттермен әуре болудың орнына бір жол
Куәліктермен қолмен жұмыс істеудің қажеті жоқ. Блогтың алдына кері прокси қояды — интернеттен келген қосылымды қабылдайтын, куәлікті ұстайтын әрі сұранысты сіздің портыңызға өткізетін бағдарлама. Олардың ең қарапайымы — Caddy, әрі оның 32-ші қадамдағы бүкіл баптауы мынадай:
blog.example.kz {
reverse_proxy 127.0.0.1:8080
encode zstd gzip
}
Мұнда куәлік туралы бір сөз жоқ — Caddy алғашқы іске қосылғанда Let’s Encrypt-ке өзі барады әрі өзі жаңартып отырады. Осы баптауды нағыз Caddy-мен тексергенде Valid configuration-нан басқа тағы бір жол шығады: enabling automatic HTTP->HTTPS redirects. Ол http-тен бағыттауды да өзі қосты — өлшемдердің екінші блогында көрінетін сол 308.
Әрі прокси сұранысты қайда өткізетініне назар аударыңыз: 127.0.0.1:8080. Осы сәттен бастап блог тек ілмекті тыңдайды, әрі интернеттен оған проксиден тыс жақындау мүмкін емес. Өткен сабақтың ережесі жойылмайды, нақтыланады: хостингте барлық мекенжайды тыңдайды, өз проксиіңнің артында — тек ілмекті.
Өтірік айтатын тақырып
Проксидің бағдарлама ішінде салдары бар. Блогқа қосылым енді әрқашан 127.0.0.1-ден келеді, әрі журналда барлық оқырманның мекенжайы біреу — проксидің мекенжайы.
Нағыз мекенжайды прокси X-Forwarded-For тақырыбына салады. Бәлесі сол — бұл тақырыпты кез келген адам жібере алады. Сондықтан оған тек қосылым проксиден келгенде ғана сенуге болады:
ip := net.ParseIP(host)
if ip == nil || !ip.IsLoopback() {
return host
}
if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" {
// ...
}
32-ші қадамда өлшенді:
ілмектен, тақырыпсыз: журналда 127.0.0.1
ілмектен, X-Forwarded-For: 1.2.3.4: журналда 1.2.3.4
желіден, X-Forwarded-For: 1.2.3.4: журналда 192.168.1.200
Үшінші жол — тексерудің бүкіл мәні: желіден келген сұраныс өзін бөтен мекенжаймен атады, әрі блог оған сенген жоқ. Бұл тексерусіз кез келген оқырман сіздің журналыңызға қалағанын жазар еді — ал журналды сіз кейін, құпиясөз теріп жүрген кім екенін анықтағанда оқисыз.
HSTS: браузерден енді http-пен жүрмеуін сұрау
http-тен бағыттау әрқашан құтқармайды: бірінші сұраныс бәрібір ашық кетті. Сондықтан сайт браузерден мұнда тек https-пен жүретінін есте сақтауын сұрайды:
strict-transport-security: max-age=63072000; includeSubDomains
Секундпен алғанда екі жыл. Алғашқы келуден кейін браузер http-ті тіпті сынап көрмейді — сұранысты жібермес бұрын оны өзі https-ке ауыстырады. Бұл проксиде бір жолмен қойылады, бірақ оны HTTPS жұмыс істеп тұрғанда қосқан жөн: браузер ұзаққа есте сақтайды.
Сабақ картасы
Өз сөзіңізбен айтыңыз
Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптар — сабақтың соңында.
- HTTPS-сіз сайтқа кіргеніңізде бөтен Wi-Fi нақты нені көреді?
- Прокси артында блог неге барлық мекенжайды емес, ілмекті тыңдайды?
X-Forwarded-Forтақырыбына неге әрқашан сенуге болмайды?
Тапсырма
Міндетті. Блогыңызға домен мен HTTPS қосыңыз: A жазбасы сіздің мекенжайыңызға апарады, блогтың алдында куәлігі бар прокси тұрады, блог 127.0.0.1-ді тыңдайды, ал журналда проксидің емес, оқырманның мекенжайы көрінеді. curl -I http://сіздің-домен арқылы тексеріңіз — онда бет емес, бағыттау болуы керек.
Мұның бәрі step-32 ішінде жасалған — өзіңіз жасағаннан кейін салыстырыңыз.
Қалауыңызша.
- Сабақтағы бағдарламаны өз машинаңызда іске қосып, бірінші блоктан өз құпиясөзіңізді көріңіз. Бұл жағымсыз көріністі бір рет көрген жөн.
- Кез келген сайттың куәлігін қараңыз:
echo | openssl s_client -connect example.com:443 | openssl x509 -noout -issuer -dates. - HSTS қосып, браузерден осы жады сақталатын бетті табыңыз (Chrome-да —
chrome://net-internals/#hsts).
Блогта бұл қайда тұрады
32-ші қадамда Caddyfile пайда болды — нағызы, ол нағыз Caddy-мен тексерілген, — әрі clientIP функциясы, соның арқасында журналда қайтадан прокси емес, оқырман көрінеді.
Қарыздар. Блог әлі күнге ұқыпты тоқтай алмайды: прокси жаңа нұсқаға ауысып та қойды, ал ескісі оқылып бітпеген жауаптарды үзіп тастайды. Бұл — келесі сабақ, қайта жүктеуден аман өтетін қызмет туралы.
Жауаптар
Жауаптарды көрсету
- Жібергеніңіздің бәрін: беттің мекенжайын, тақырыптарды әрі форманың мазмұнын. Ашық арнада құпиясөз толық, әріптермен көрінеді — бұл бағдарлама шығысының бірінші блогы.
- Өйткені сырттан тек прокси көрінуі керек: ол куәлікті ұстайды әрі TLS-ті талдайды. Блог барлық мекенжайды тыңдаса, оған проксиден тыс
http-пен жақындауға болар еді, әрі куәліктің бүкіл мәні жоғалар еді. - Өйткені оны кез келген адам жібере алады: бұл кәдімгі сұраныс тақырыбы. Оған қосылым проксиден келгенде ғана сенуге болады — сонда оны оқырман емес, прокси қойған.
Пікірлер (0)
Пікір қалдыру үшін кіріңіз →
Әзірге пікір жоқ. Бірінші болыңыз.