Shanraq.org Shanraq.org
Өз доменің және HTTPS: блогты атпен ашсын
IT

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 жұмыс істеп тұрғанда қосқан жөн: браузер ұзаққа есте сақтайды.

Сабақ картасы

Сабақ картасы: домен, прокси және TLS

Өз сөзіңізбен айтыңыз

Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптар — сабақтың соңында.

  1. HTTPS-сіз сайтқа кіргеніңізде бөтен Wi-Fi нақты нені көреді?
  2. Прокси артында блог неге барлық мекенжайды емес, ілмекті тыңдайды?
  3. 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 функциясы, соның арқасында журналда қайтадан прокси емес, оқырман көрінеді.

Қарыздар. Блог әлі күнге ұқыпты тоқтай алмайды: прокси жаңа нұсқаға ауысып та қойды, ал ескісі оқылып бітпеген жауаптарды үзіп тастайды. Бұл — келесі сабақ, қайта жүктеуден аман өтетін қызмет туралы.

Жауаптар

Жауаптарды көрсету
  1. Жібергеніңіздің бәрін: беттің мекенжайын, тақырыптарды әрі форманың мазмұнын. Ашық арнада құпиясөз толық, әріптермен көрінеді — бұл бағдарлама шығысының бірінші блогы.
  2. Өйткені сырттан тек прокси көрінуі керек: ол куәлікті ұстайды әрі TLS-ті талдайды. Блог барлық мекенжайды тыңдаса, оған проксиден тыс http-пен жақындауға болар еді, әрі куәліктің бүкіл мәні жоғалар еді.
  3. Өйткені оны кез келген адам жібере алады: бұл кәдімгі сұраныс тақырыбы. Оған қосылым проксиден келгенде ғана сенуге болады — сонда оны оқырман емес, прокси қойған.

Дереккөздер

Мәтінде қате не теру қатесі кездессе, бізге айтыңыз

Тапсырманы тексеру

Алдымен VS Code-та шешіп, іске қосыңыз — редактор қатені сол жерде көрсетеді. Дайын шешімді осында қойыңыз. Тексеретін — модель: ол қатені атап көрсетеді, бірақ дайын жауапты бермейді.

Тексеру үшін кіру керек. Кіру

Пікірлер (0)

Әзірге пікір жоқ. Бірінші болыңыз.