Help us shape the future of Go! Take the 2026 Go Developer Survey

upstream

package
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package upstream implements the outbound (client-side) half of the bastion: dialing targets and opening channels on their behalf.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnreachable: TCP bağlantısı hiç kurulamadı.
	ErrUnreachable = errors.New("upstream: target unreachable")

	// ErrHostKeyMismatch: hedef, sabitlenmişten BAŞKA bir anahtar sundu.
	// Bu bir yapılandırma sorunu olabilir de olmayabilir de — postern
	// bu ikisini ayırt edemez ve etmemeli.
	ErrHostKeyMismatch = errors.New("upstream: host key mismatch")

	// ErrRefused: taşıma kuruldu, hedef sertifikamızı kabul etmedi.
	// Neredeyse her zaman tek bir şey demek: hedef bu bastion'ın CA'sına
	// güvenmiyor (TrustedUserCAKeys) ya da kullanıcı hedefte yok.
	ErrRefused = errors.New("upstream: target refused our certificate")

	/*
	 * ErrHandshake: TCP kuruldu ama el sıkışma KİMLİK DOĞRULAMAYA
	 * GELMEDEN bitti.
	 *
	 * ⚠️ REDDEDİLMİŞ DEĞİLİZ — hedef sertifikamızı görmedi bile.
	 * Algoritma uyuşmazlığı, susan bir hedef, SSH konuşmayan bir port:
	 * üçü de burada. Operatörün yapması gereken şey ErrRefused'dakiyle
	 * hiç ilgisi olmayan bir şey, o yüzden ayrı bir sınıf.
	 */
	ErrHandshake = errors.New("upstream: ssh handshake did not complete")
)

* Bağlantının NEDEN kurulamadığı. * * ⚠️ NEDEN AYRIŞTIRILIYOR: üçü de "bağlanamadım" ama operatörün yapması * gereken şey üçünde de farklı — biri ağ/firewall, biri hedefin * kimliğinin değişmesi (ya da araya girme), biri hedefin bu bastion'ın * CA'sına güvenmemesi. Hepsini tek bir "session unavailable" altında * toplamak, kullanıcıya hiçbir şey söylemeyen bir ekran bırakıyordu: * ÖLÇÜLDÜ — panelde kabuk düğmesine basınca tek görünen şey * "[disconnected]" idi, sebep yalnızca sunucu günlüğünde duruyordu.

View Source
var CapabilityCommands = []string{
	"sudo -n -l",

	"for n in useradd adduser groupadd addgroup usermod userdel groupdel visudo bash; do command -v $n; done",

	"sudo -n sshd -T 2>/dev/null | awk 'tolower($1)==\"authorizedprincipalsfile\"{print $2}'",
}

* CapabilityCommands, yönetilebilirliği ölçen SABİT komutlar. * * Probe'daki kuralın aynısı: derleme zamanında sabit, yapılandırılamaz, * salt okuma ve kabuk yorumuna açık hiçbir şey içermiyor. Operatörün * komut yazabildiği bir alan, config'e erişen herkese bütün filoda * uzaktan komut çalıştırma yetkisi verirdi. * * ⚠️ SON SÖZÜ ARACIN KENDİSİ SÖYLÜYOR. "sudoers dosyasını yazdık" * demek, sudo'nun onu OKUDUĞU anlamına gelmiyor: sudoers.d ancak ana * dosyada bir includedir satırı varsa okunuyor ve sıkılaştırılmış * kurulumlarda o satır kaldırılmış olabiliyor. `sudo -n -l` postern'in * KENDİ hakkını listeliyor; çıktı doluysa hem sudo çalışıyor hem de * kuralımızın okunduğu kanıtlanmış oluyor.

View Source
var ErrNoAnswer = errors.New("upstream: the target did not report whether the command ran")

* ErrNoAnswer: hedef, komutun çalışıp çalışmadığını BİLDİRMEDİ. * * Üç ayrı durum aynı sınıfa düşüyor ve düşmeli: kanal hiç açılamadı, * hedef exec isteğini reddetti, ya da kanal çıkış kodu gelmeden kapandı. * Üçünde de elimizdeki tek doğru cümle "bilmiyoruz". * * ⚠️ İLK ADI "ErrLost" ("bağlantı koptu") İDİ VE YANLIŞTI: exec isteğini * reddeden hedef bağlantıyı koparmıyor, açıkça cevap veriyor. Operatöre * "makine gitti" demek onu ağa baktırırdı. Sebebi ayırt etmek için * x/crypto'nun hata metnini ayrıştırmıyoruz (bkz. hostkey.go: bir * bağımlılığın dizgisi değiştiği gün sessizce yanlış sınıfa düşülür); * kütüphanenin mesajı %w zincirinde operatöre ulaşıyor. * * ⚠️ ErrUnreachable DEĞİL: o, TCP'nin hiç kurulmadığını söylüyor.

View Source
var ProbeCommands = []string{
	"uname -srm",
	"cat /etc/os-release",
}

* ProbeCommands, tanımada çalıştırılan SABİT komutlar. * * ⚠️ DEĞİŞKEN DEĞİL, SABİT — ve bu listenin yapılandırılabilir olmaması * özelliğin en önemli parçası. Operatörün komut yazabildiği bir alan, * config dosyasına erişebilen herkese denetim altındaki HER MAKİNEDE * uzaktan komut çalıştırma yetkisi verirdi; bastion'ı tam da engellemek * için var olduğu şeye çevirirdi. Bu yüzden liste derleme zamanında * sabit ve gözden geçirilebilir. * * İkisi de SALT OKUMA ve yan etkisiz. Kabuk yorumuna açık hiçbir şey * içermiyorlar (joker, boru, yönlendirme, değişken yok) — SSH exec * isteği dizeyi hedefin kabuğuna verdiği için bu önemli.

Functions

func ScanHostKey

func ScanHostKey(ctx context.Context, host string, port int) (ssh.PublicKey, error)

* ScanHostKey, hedefin SUNDUĞU host key'i getirir. * * ⚠️ BU BİR DOĞRULAMA DEĞİL, BİR SORU. Dönen anahtar "doğru" anahtar * değil; o adreste o anda cevap veren makinenin sunduğu anahtar. Ağ * yolunda duran biri kendi anahtarını sunabilir ve buradan onu alırız. * * Yine de yapıştırmalı akıştan daha kötü DEĞİL: operatör de * `ssh-keyscan`i kendi makinesinden, çoğu zaman aynı ağ üzerinden * çalıştırıp yapıştırıyordu. Kazanılan şey yazım hatasının ve "şimdilik * boş bırakayım" cazibesinin ortadan kalkması; kaybedilen bir şey yok. * Güvenlik, çağıranın parmak izini operatöre AÇIKÇA gösterip onay * istemesinden geliyor — bkz. httpapi.adminScanTarget.

Types

type CommandError added in v1.2.0

type CommandError struct {
	Status int
	Stderr string
}

* CommandError: hedef komutu ÇALIŞTIRDI ve sıfırdan farklı bir kodla * bitirdi. * * ⚠️ BU BİR ARIZA DEĞİL, BİR CEVAP. `command -v` bulamadığı ad için, * `sudo -n -l` yetkisiz hesap için sıfırdan farklı dönüyor; çağıran * hangisinin cevap hangisinin arıza olduğuna errors.As ile karar * veriyor. stderr burada taşınıyor çünkü operatörün okuyacağı sebep * ("useradd: group 'x' does not exist") yalnızca orada yazıyor.

func (*CommandError) Error added in v1.2.0

func (e *CommandError) Error() string

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is an established SSH connection to a target.

func DialManagement added in v1.2.0

func DialManagement(ctx context.Context, t model.Target, authority *ca.CA, actor, reason string) (*Conn, error)

* DialManagement, hedefe yönetim hesabıyla bağlanır. * * ⚠️ actor VE reason ZORUNLU ve sertifikanın KeyID'sine giriyor. KeyID * hedefin KENDİ sshd günlüğüne düşüyor ("Accepted publickey for postern * ... ID postern-manage: admin: check"). Makinenin sahibi postern'in * denetim kaydını göremiyor olabilir; o satırda düğmeye kimin bastığı * yazmıyorsa, filo çapında bir root girişi hiçbir kişiye * bağlanamazdı.

func DialWithCert

func DialWithCert(ctx context.Context, t model.Target, identity Identity, authority *ca.CA) (*Conn, error)

DialWithCert connects to t using a freshly minted, short-lived certificate instead of a static key.

func (*Conn) Capabilities added in v1.2.0

func (c *Conn) Capabilities(ctx context.Context) (ManageCapabilities, error)

* Capabilities, hedefte sabit komutları çalıştırıp yeteneği ölçer. * * ⚠️ HATA "YÖNETİLEBİLİR DEĞİL" DEMEK DEĞİL. Bağlantı koptuğunda * bilmediğimiz şeyi bilmiyoruz; onu "yönetilemez" diye kaydetmek, * ölçmediğimiz bir sonucu ölçülmüş gibi göstermek olurdu. * * ⚠️ BU KURAL YAZILIYDI AMA UYGULANMIYORDU. İlk hâli her hatayı yutup * boş çıktıyı ayrıştırıyordu: kopan bir bağlantı "sudo yok, useradd yok, * visudo yok" diye, hatasız dönüyordu. Sıfırdan farklı çıkış bir cevap * (CommandError), cevapsızlık ise hata.

func (*Conn) Client

func (c *Conn) Client() *ssh.Client

Client exposes the underlying SSH client

func (*Conn) Close

func (c *Conn) Close() error

Close closes the connection to the target.

func (*Conn) Exec added in v1.2.0

func (c *Conn) Exec(ctx context.Context, command, stdin string) (string, error)

* Exec, komutu hedefte çalıştırır ve STDOUT'u döner. * * ⚠️ STDOUT VE STDERR AYRI TUTULUYOR — BİRLEŞTİRMEK VERİYİ BOZUYOR. * Dönen dize yalnızca bir kayıt değil, VERİ olarak da okunuyor: plan, * hedefteki sudoers dosyasını `sudo -n cat` ile okuyup yazacağıyla * bayt bayt karşılaştırıyor. Bu makinelerde çok sık görülen * "sudo: unable to resolve host" uyarısı stdout'a karışsaydı, dosyalar * hiç eşleşmez ve plan her koşuda aynı dosyayı yeniden yazardı. Hata * sebebi kaybolmuyor: CommandError'da taşınıyor. * * Komut bir KABUĞA gidiyor (SSH exec): planın adımları `>/dev/null` ve * `&&` içeriyor ve bunlar argv'ye bölünseydi, tee adımı ">/dev/null" * adında bir dosya yaratıp sudoers dosyasını hiç yazmazdı.

func (*Conn) Facts

func (c *Conn) Facts() model.TargetFacts

Facts, EL SIKIŞMADAN öğrenilenleri döner.

⚠️ KAPSAM KASITLI OLARAK DAR. Buradan çıkan her şey sunucunun bağlantı kurarken kendiliğinden söylediği: afişi, anlaştığımız host key türü, bir de ne kadar sürdüğü. Hedefte `uname` ya da `/etc/os-release` okumak çok daha fazlasını verirdi — ve bunu yapan iki yol da (target_probe, yönetim bağlantısı) varsayılan olarak kapalı ve buraya YAZMIYOR: bu yapı yalnızca el sıkışmada öğrenileni tutuyor. Bir bastion'ın kendiliğinden envanter aracına dönüşmesi, denetim altındaki her makinede sessizce komut çalıştırması demek.

func (*Conn) OpenSession

func (c *Conn) OpenSession() (ssh.Channel, <-chan *ssh.Request, error)

OpenSession opens a "session" channel on the target and returns it along with the target's request stream.

func (*Conn) Probe

func (c *Conn) Probe(ctx context.Context) (model.TargetProbe, error)

* Probe, hedefte sabit okuma komutlarını çalıştırıp makineyi tanır. * * ⚠️ BU FONKSİYON YALNIZCA target_probe.enabled İLE ÇAĞRILIR. Kapalıyken * postern hedefte kullanıcının oturumu dışında hiçbir şey çalıştırmaz. * * ⚠️ KOMUTLAR KULLANICININ BAĞLANTISINDA ÇALIŞIR. Bu yorum önceden * "postern'in kullanıcılardan bağımsız bir erişimi yok" diyordu; yönetim * hesabıyla (manage.go) artık var — ama yalnızca rolü * postern_manage_host ile koşmuş hedeflerde, ve bilinçli olarak burada * KULLANILMIYOR. Tanımayı ona taşımak iki şeyi bozardı: yönetim hesabı * olmayan hedeflerde tanıma tamamen kapanırdı, ve `uname` okumak için * parolasız root tutan bir kimlik kullanmak gereğinden geniş bir yetkiyle * salt okuma yapmak olurdu. Bedeli şu: komutlar hedefin günlüklerinde * bağlanan kullanıcının adına görünür. Kabul edilebilir olmasının tek * sebebi özelliğin varsayılan kapalı ve her koşusunun denetime yazılıyor * olması.

type Identity

type Identity struct {
	PosternUser string

	OSUser string
}

type ManageCapabilities added in v1.2.0

type ManageCapabilities struct {
	// Sudo, postern'in parolasız sudo hakkının GÖRÜLDÜĞÜ.
	Sudo bool

	// AddUser / AddGroup, bulunan araçların tam yolu ("" ise yok).
	AddUser  string
	AddGroup string
	ModUser  string
	DelUser  string
	DelGroup string
	Visudo   string

	// Family, os-release'den çıkan aile ("debian", "rhel", "alpine"…).
	Family string
	/*
	 * Shell, açılan hesaba yazılacak kabuk: bash bulunduysa onun yolu,
	 * yoksa /bin/sh. ⚠️ ÖLÇÜLDÜ: /bin/bash sabit yazılıydı ve Alpine'de
	 * hesap açılıyor, sertifika kabul ediliyor ve sshd "shell /bin/bash
	 * does not exist" diye kapıyı kapatıyordu.
	 */
	Shell string

	// Missing, eksik olanların adı — panelin yazacağı cümle bu.
	Missing []string
	/*
	 * PrincipalsFile, sshd'nin etkin AuthorizedPrincipalsFile deseni
	 * ("/etc/ssh/auth_principals/%u"). Boşsa ya sshd "none" diyor (giriş
	 * adı sertifikanın principal'ında aranır, dosya gerekmez) ya da
	 * sorulamadı. Geçici hesabın sertifikayla açılabilmesi için plan bu
	 * desene göre hesabın dosyasını yazıyor.
	 */
	PrincipalsFile string
}

* ManageCapabilities, hedefte neyin bulunduğu. * * ⚠️ ARAÇ ADLARI SAKLANIYOR, "var/yok" DEĞİL. useradd ile busybox * adduser'ın bayrakları farklı; hangisinin bulunduğunu bilmeden doğru * komutu kuramayız ve "adduser vardır" varsayımı Alpine'de sessizce * yanlış komut üretirdi.

func ParseCapabilities added in v1.2.0

func ParseCapabilities(sudoOut, whichOut, osRelease, sshdPrincipals string) ManageCapabilities

* ParseCapabilities, sabit komutların çıktısını okur. * * Ayrı bir fonksiyon çünkü arıza burada yaşıyor: bağlantı kurmadan, * gerçek dağıtım çıktılarına karşı ölçülebiliyor.

func (ManageCapabilities) Manageable added in v1.2.0

func (c ManageCapabilities) Manageable() bool

* Manageable, postern'in bu hedefi yönetip yönetemeyeceği. * * ⚠️ POZİTİF KANIT İSTİYOR. "Eksik bir şey görmedim" yetmiyor; her * parçanın BULUNDUĞU görülmüş olmalı. Yoklama yarıda kesildiyse * (bağlantı koptu, komut boş döndü) sonuç "yönetilebilir" olmamalı.

func (ManageCapabilities) Summary added in v1.2.0

func (c ManageCapabilities) Summary() string

Summary, panelde ve denetim satırında görünecek tek cümle.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL