Documentation
¶
Overview ¶
Package model holds postern's domain types.
S2'de bu tipler config'den elle kuruluyordu; S3'ten beri veritabanından geliyorlar (şema bu alanların birebir karşılığı). Kararları veren kod model tiplerine baktığı için, kaynağın değişmesi policy'yi etkilemedi — motorun SQLite'tan PostgreSQL'e geçmesi de bu pakete dokunmadı.
Index ¶
- Constants
- func ControlCharAt(s string) int
- func IsManagementName(name string) bool
- func ResolvedGroups(groups []string) []string
- func ValidOSUserName(name string) bool
- func ValidUsername(name string) error
- type PathRule
- type Role
- type SFTPJournal
- type Session
- type Target
- type TargetFacts
- type TargetProbe
- type TemporaryAccess
- type User
Constants ¶
const ( // ManagementAccount, hedefteki yönetim hesabının OS adı. ManagementAccount = "postern" // ManagementPrincipal, o hesabı açan tek sertifika principal'ı. ManagementPrincipal = "postern-manage" )
* Yönetim hesabının adları. * * ⚠️ BU İKİ AD, HEDEFTE PAROLASIZ ROOT SUDO TUTAN HESABIN ANAHTARI. * deploy/ansible/roles/postern_target, "postern" hesabını açıyor ve onu * yalnızca "postern-manage" principal'ı taşıyan bir sertifikayla * açılabilir kılıyor. Bu yüzden adlar tek bir yerde duruyor: store, * policy ve upstream aynı sabiti okumazsa, birinin reddettiği adı öbürü * sessizce kabul eder. * * ⚠️ YAPILANDIRILAMAZ. Ansible rolünde değişken olarak duruyorlar ama * rol, varsayılandan farklı bir değeri REDDEDİYOR. Operatörün adı * değiştirebildiği bir kurulumda ikili eski adı reddetmeye devam eder, * yeni adı ise sıradan bir hesap sanardı.
const UnknownGroup = "unknown"
* UnknownGroup, kaynağın CEVAP VERDİĞİ ama hiçbir grup söylemediği * kullanıcıların düştüğü grup. * * ⚠️ ÇÖZÜLDÜ AMA BOŞ ≠ ÇÖZÜLEMEDİ. Bu grup YALNIZCA kaynak "bu kişiyi * tanıyorum ve hiçbir grupta değil" dediğinde uygulanır. Dizin cevap * veremediğinde ya da kullanıcıyı hiç bulamadığında uygulanmaz — o iki * hâlde hiçbir şey bilinmiyor demektir ve bir arızayı yetkiye çevirmek, * default-deny'ın tam tersi olurdu. * * Var olma sebebi somut: grup claim'i göndermeyen bir IdP'de hiç kimse * hiçbir role eşleşmiyor, ProvisionUser hesabı AÇMIYOR ve kullanıcı * kapıda kalıyor — yöneticinin elinde onu düzeltecek bir tutamak bile * olmadan. Bu grup o tutamağı veriyor: yönetici `unknown`'ı bir role * eşler, kullanıcı içeri girer ve doğru grubuna elle atanır.
Variables ¶
This section is empty.
Functions ¶
func ControlCharAt ¶ added in v1.2.0
* ControlCharAt, dizgideki ilk kontrol karakterinin bayt konumu; yoksa -1. * * ⚠️ TEK TANIM, İKİ KAPI. Aynı kural hem SERTİFİKA İMZALANIRKEN * (ca.Sign: key id ve principal) hem de kullanıcı adı VERİTABANINA * YAZILIRKEN uygulanıyor. İki ayrı kopya yazmak, ikisinin ayrışması * demekti — ve ayrışmanın yönü fark ediyor: yazma kuralı imzalama * kuralından GEVŞEK olursa, açılabilen ama sertifikası hiç kesilemeyen * bir hesap doğar. Bu depo o arızayı bir kez ölçtü (os_user kuralı * yalnızca politika kapısındaydı; hesap "kurulmuş görünüp her oturumda * reddedilen" hâlde doğuyordu, bkz. refuseBadOSUser). * * ⚠️ C1 DE SAYILIYOR (U+0080-U+009F), imzalama tarafındaki bayt * taramasının kaçırdığı aralık: UTF-8'de C2 80 olarak kodlanıyor ve iki * baytın ikisi de 0x20'nin üstünde. Terminal ve günlük ayrıştırıcıları * onları da yorumluyor. Kural bu yüzden rune üzerinden.
func IsManagementName ¶ added in v1.2.0
* IsManagementName, adın yönetim hesabına ait olup olmadığı. * * ⚠️ İKİSİ DE REDDEDİLİYOR, YALNIZCA HESAP ADI DEĞİL. Sıradan oturumlarda * sertifikanın principal'ı kullanıcının os_user'ı. os_user'ı * "postern-manage" olan bir panel kullanıcısı için postern tam da yönetim * principal'ını imzalardı; os_user'ı "postern" olan biri ise yönetim * hesabına giriş denerdi. Hedefin principal dosyası bugün ikisini de * durduruyor — ama elle kurulmuş, eski bir playbook'la ya da başka bir * araçla yapılandırılmış bir makinede o dosya daha gevşek olabilir ve * postern bunu göremez. Savunma hedefe bırakılmıyor.
func ResolvedGroups ¶
* ResolvedGroups, çözülmüş bir grup listesini role çevrilmeye hazırlar. * * ⚠️ YALNIZCA PRESENCE=PRESENT olan çağrı noktalarından çağrılmalı. * İmzası bunu zorlayamıyor (bir []string alıyor), o yüzden kural her * çağrı yerinde yazılı: "kaynak cevap verdi" bilgisi orada duruyor, * burada değil.
func ValidOSUserName ¶
ValidOSUserName reports whether name may be used as a target account.
func ValidUsername ¶ added in v1.2.0
* ValidUsername, adın postern kullanıcı adı olarak yazılabilir olduğu. * * ⚠️ YALNIZCA KONTROL KARAKTERİ ELENİYOR, ŞEKİL DEĞİL. Kullanıcı adı * kimlik sağlayıcısından geliyor ve e-posta, UPN ya da uzun bir dizin * adı olabiliyor; ona bir desen dayatmak kurumun kendi ad alanını * reddetmek olurdu. Elenen şey ÖLÇÜLEN zarar: ad, sertifikanın key * id'sine giriyor ve hedefin sshd günlüğüne olduğu gibi yazılıyor — * satır sonu taşıyan bir ad, o günlüğe kendi satırını yazdırıyordu.
Types ¶
type PathRule ¶ added in v1.2.0
type PathRule struct {
// Prefix, mutlak yol öneki. Eşleşme DİZİN SINIRINDA yapılıyor.
Prefix string
// Allow false ise açık ret: izinli bir ağacın içinden dal kesmeye
// yarıyor. En uzun eşleşen önek kazandığı için çalışıyor.
Allow bool
// CanWrite, iznin yazmayı da kapsayıp kapsamadığı.
CanWrite bool
}
PathRule, bir yol öneği üzerindeki karar.
type Role ¶
type Role struct {
Name string
// Targets, bu rolün erişebildiği hedef adları.
Targets []string
/*
* Paths, bu rolün SFTP yol kuralları.
*
* ⚠️ BOŞ LİSTE "HİÇBİR ŞEY" DEĞİL, "KISIT YOK" DEMEK. Kuralı olmayan
* bir rol bugünkü gibi her yola erişiyor; kısıtlama kural
* EKLENDİĞİNDE başlıyor. Aksi hâli, yükseltmenin ertesi sabahı her
* kurulumun SFTP'sini kırmak olurdu.
*/
Paths []PathRule
}
Role, bir hedef kümesine erişim yetkisi.
S3 şemasında roles + role_targets tablolarının karşılığı.
type SFTPJournal ¶ added in v1.2.0
type SFTPJournal struct {
/*
* Measured, kaydın mühür satırındaki sayının bilindiği.
*
* ⚠️ FALSE "GEÇTİ" DEĞİL, "KARŞILAŞTIRILAMAZ" DEMEK — zincir
* başındaki (034) boş baş kararının aynısı. Göç 037'den önce
* kapanmış oturumlarda ve kaydı hiç tutulmamış oturumlarda mühür
* yok; onları "defteri tam" diye göstermek, hiç yapılmamış bir
* kontrolü yapılmış saymak olurdu.
*/
Measured bool
// Events, kaydın mühür satırındaki olay sayısı (internal/sftpcast).
Events int64
/*
* Digest, mühür satırındaki özet: kayda giren satırların
* XOR'lanmış SHA-256'sı, onaltılık.
*
* ⚠️ SAYININ YANINDA DURUYOR ÇÜNKÜ AYRI BİR SORUYU CEVAPLIYOR.
* Sayı "kaç satır" diyor; özet "hangi satırlar" diyor. Sayı tutup
* özet tutmuyorsa satır SİLİNMEMİŞ, DEĞİŞTİRİLMİŞ demektir — ve o,
* defterden satır silmekten daha ince bir müdahale: silinen satır
* en azından bir boşluk bırakıyor, değiştirilen satır tam bir
* denetim kaydı gibi duruyor.
*
* ⚠️ OLAY YOKKEN BOŞ — sıfırların onaltılığı DEĞİL. Mühür satırı
* da yazılmıyor; dosyada karşılığı olmayan bir değeri saklamak,
* kontrolün "kayıt böyle diyor" dediği şeyi uydurmak olurdu.
*/
Digest string
/*
* Lost, postern'in deftere koyamadığını BİLDİĞİ olay sayısı:
* tampon taştığı için atılanlar ve kapanışta yazılamamış olarak
* elde kalanlar.
*
* ⚠️ AYRI DURUYOR ÇÜNKÜ AYRI BİR BULGU. Aritmetiği "satırlar
* silinmiş" hâliyle aynı (satır sayısı mühürden eksik) ama olayı
* bambaşka: biri postern'in kendi arızası, öbürü müdahale.
*/
Lost int64
/*
* Denied, POSTERN'İN KENDİ reddettiği istek sayısı — defterdeki
* "denied." önekli satırlar (sftpaudit/policy.go).
*
* ⚠️ HEDEFİN "permission denied"I BURAYA GİRMİYOR. O, hedefin dosya
* izinleri hakkında bir şey söylüyor; bu, postern'in kurallarının
* çiğnenmeye çalışıldığını. İkisini tek sayıya katlamak, denetçiye
* "kural sınandı" ile "kullanıcının o dosyaya erişimi yok"u aynı
* rakamla gösterirdi.
*
* ⚠️ SAYI, DEFTERDEKİ SATIR ADEDİNDEN BÜYÜK OLABİLİR. Ardışık aynı
* retler tek satıra katlanıyor ve sayı katlananları da içeriyor:
* "kaç satır var" ile "kaç kez denendi" apayrı iki soru, ve
* denetçinin sorduğu ikincisi.
*
* ⚠️ Counted FALSE İKEN SIFIR OKUNMAMALI. Göç 038'den önce kapanmış
* oturumlarda sayım yapılmadı; sıfır göstermek "hiçbir şey
* reddedilmedi" demek olurdu — Measured'ın aynı gerekçesi.
*/
Denied int64
// Counted, Denied'in gerçekten sayıldığı (göç 038 sonrası).
Counted bool
}
* SFTPJournal, bir oturumun SFTP denetim defterinin kapanıştaki durumu. * * ⚠️ ÜÇ ALAN, ÇÜNKÜ ÜÇ AYRI SORU. "Kayıt kaç olay saydı", "postern * bunların kaçını deftere koyamadığını BİLİYOR" ve "bu ikisi ölçüldü * mü". Üçüncüsü olmadan diğer ikisi yanıltıcı: ölçülmemiş bir oturumda * Events sıfırdır ve sıfır, "hiç olay olmadı" gibi okunur.
type Session ¶
type Session struct {
// ID, record.NewSessionID()'nin ürettiği değer. Kayıt dosyasının adı
// da bu olduğu için, denetim kaydı ile .cast dosyası arasındaki bağ
// tek bir alanla kuruluyor.
ID string
// User ve Target, id değil AD tutar. Denetim kaydını okuyan insan
// "hangi kullanıcı" diye sorduğunda cevabın bir JOIN'e ihtiyacı olmasın.
User string
Target string
// OSUser, oturumun AÇILDIĞI hesap.
//
// users.os_user'ın kopyası gibi görünür ama değildir: policy o an
// başka bir hesaba karar vermiş olabilir ve kullanıcının bugünkü
// os_user'ı o günkü kararı değiştirmemeli. Denetim, kaydın alındığı
// andaki gerçeği saklar.
OSUser string
// SrcIP, kullanıcının bastion'a bağlandığı adres.
SrcIP string
/*
* Temporary: oturumu bir ROL değil, SÜRELİ BİR HAK açtı (göç 044).
*
* ⚠️ KARAR ANINDA YAZILIYOR, SONRADAN TÜRETİLMİYOR. Hak geri
* alındığında ya da süresi dolduğunda jit_grants'tan bakarak "o
* saatte açık bir hak var mıydı" demek, denetim kaydını başka bir
* tablonun sonraki hâline bağlar; denetçi bunu satırda görmeli.
*/
Temporary bool
// RecordingChain ve RecordingLinks, kaydın zincir başı ve halka
// sayısı (göç 034).
//
// ⚠️ BOŞ BAŞ "DOĞRULANDI" DEĞİL, "DOĞRULANAMAZ" DEMEK. Göç öncesi
// kapanmış oturumlarda ve çökme sonrası süpürülen oturumlarda baş
// yok; ikisini "geçti" gibi göstermek, kanıtı olmayan bir kaydı
// kanıtlanmış saymak olurdu.
RecordingChain string
RecordingLinks int64
// SFTPJournal, oturum kapanırken defterin durumu (göç 037).
SFTPJournal SFTPJournal
StartedAt time.Time
// EndedAt sıfır değerse oturum hâlâ açık (şemada NULL).
EndedAt time.Time
// RecordingPath, .cast dosyasının yolu. Kayıt açılamadıysa boş —
// ama o durumda oturum zaten reddedilmiş olmalı (S1.8 kararı).
RecordingPath string
}
Session, açılmış bir oturumun denetim kaydı.
S3 şemasında sessions tablosu. Bu kayıt oturumdan DAHA UZUN yaşar: kullanıcı ya da hedef sonradan silinse bile "kim, nereye, ne zaman girdi" sorusunun cevabı durmak zorunda.
type Target ¶
type Target struct {
Name string
Host string
Port int
HostKey string
// Labels, operatörün iliştirdiği key=value notları ("env=prod").
//
// ⚠️ YETKİ DEĞİL: erişimi rol → hedef bağı veriyor. Etiket yalnızca
// hedefleri gruplayıp bulmak için — etiketten yetki türetmek,
// etiket ekleyebilen herkese yetki dağıtmak olurdu.
//
// Yalnızca LİSTELEME yollarında dolu (store.Targets). Oturum açma
// yolundaki tekil okuma bunu çekmiyor: her bağlantıda ikinci bir
// sorgu, hiç kullanılmayan bir alan için.
Labels map[string]string
}
type TargetFacts ¶
type TargetFacts struct {
// ServerVersion, sunucunun kendi afişi: "SSH-2.0-OpenSSH_9.6p1 Debian-3".
ServerVersion string
// HostKeyType, pinlenmiş anahtarın türü (ssh-ed25519 …).
HostKeyType string
// LastSeenAt, son BAŞARILI bağlantı. Sıfır ise hiç bağlanılmadı.
LastSeenAt time.Time
// ConnectMS, o bağlantının el sıkışma süresi.
ConnectMS int
// LastErrorAt/LastError, son BAŞARISIZ deneme. Başarıyı silmiyor.
LastErrorAt time.Time
LastError string
// Probe, yalnızca target_probe.enabled ile dolar — yani hedefte
// komut çalıştırıldıysa. ProbedAt sıfırsa hedefe hiç dokunulmadı.
//
// İç içe duruyor ki paneli çizen de, koda bakan da "bu satırı
// öğrenmek için makineye dokunduk mu" sorusunu tek bakışta
// cevaplayabilsin.
Probe TargetProbe
ProbedAt time.Time
}
TargetFacts, hedefe BAĞLANIRKEN öğrenilenler.
⚠️ Yapılandırma değil GÖZLEM: Target operatörün yazdığını, bu yapı makinenin söylediğini tutuyor. İçeriği el sıkışmadan ve (açıksa) kullanıcının kendi bağlantısındaki tanımadan geliyor.
⚠️ Bu yorum önceden "postern kullanıcının oturumu dışında hedefte iş çalıştırmaz" diyordu. Yönetim hesabıyla bu artık yalnızca rolü postern_manage_host ile koşmuş hedefler için doğru değil — ve o koşuların sonucu BURAYA yazılmıyor: bu yapı hâlâ yalnızca bağlanırken öğrenilenleri tutuyor.
type TargetProbe ¶
type TargetProbe struct {
// Kernel, `uname -srm` çıktısı.
Kernel string
// OSName, /etc/os-release içindeki PRETTY_NAME (yoksa NAME).
OSName string
}
TargetProbe, hedefte KOMUT ÇALIŞTIRARAK öğrenilenler.
⚠️ TargetFacts'ten AYRI TUTULUYOR ve bu ayrım kasıtlı: TargetFacts'in içeriği el sıkışmadan gelir ve postern hedefte hiçbir şey çalıştırmadan öğrenilir. Buradakiler ise ancak target_probe.enabled ile, hedefte komut koşturularak elde ediliyor. İkisini aynı yapıya koymak, panele bakan operatörün "bunu öğrenmek için makineye dokunduk mu" sorusunu cevapsız bırakırdı.
type TemporaryAccess ¶ added in v1.2.0
type TemporaryAccess struct {
Target string
// OSUser, hedefte açılan hesabın adı. Kişinin o günkü os_user'ı;
// sertifikanın principal'ı da bu olmak zorunda.
OSUser string
ExpiresAt time.Time
/*
* Applied, hesabın hedefte GERÇEKTEN açılmış olduğu. Yarım kalan bir
* hak da kayıtta duruyor (süpürücü toplayacak); ona yetki vermek,
* var olmayan bir hesaba oturum açtırmaya çalışmak olurdu.
*/
Applied bool
}
* TemporaryAccess, bir kişinin bir hedefte SÜRELİ hesabı — politikanın * gördüğü hâli (jit_grants satırının yetkiye dair kısmı). * * ⚠️ ROL DEĞİL, AYRI BİR YETKİ KAYNAĞI. Rol, rolü taşıyan herkese kalıcı * erişim; bu ise tek kişiye, tek hedefte, bir vadeye kadar. İkisini aynı * yapıya sıkıştırmak — geçici hakkı bir role çevirmek — süresi dolunca * silinmesi gereken şeyi rol tablosuna sızdırırdı.
type User ¶
type User struct {
// Name, postern kullanıcı adı ("yigit"). auth.go'nun doğruladığı ve
// Permissions'a koyduğu değer.
Name string
// OSUser, kişinin hedeflerdeki VARSAYILAN hesabı. İstek boş geldiğinde
// principal bu olur.
//
// S3 şemasında users.os_user (NOT NULL) — kişiye özel, paylaşılmaz.
// Driver 1'in özü bu alan: herkes hedefe kendi adıyla düşüyor.
OSUser string
// Roles, kişinin sahip olduğu roller. Hedef erişimi rollerden gelir.
Roles []Role
// SSOOnly true ise bu kullanıcı YALNIZCA kimlik sağlayıcı üzerinden
// girebilir; public key ile girişi reddedilir.
//
// IdP'den otomatik oluşan (JIT) kullanıcılar böyle doğar: erişimleri
// IdP'de kapatılınca gerçekten bitsin ve rolleri her girişte
// tazelensin diye. Elle oluşturulan servis hesapları false kalır.
SSOOnly bool
/*
* DirBound, hesabın bir DİZİN kimliğine bağlı olduğu.
*
* ⚠️ Oturum açılışında dizine yeniden sorulup sorulmayacağının
* DOĞRU koşulu bu — SSOOnly değil. Ölçüldü: yetkisi dizinden gelen
* bir yönetici (admin_via='group') sso_only=false ile duruyordu,
* yani dizinde kapatılsa bile anahtarıyla oturum açardı.
*/
DirBound bool
// Admin, uygulama YÖNETİM yetkisi (kullanıcı/rol/hedef değiştirme,
// web'deki yönetim sayfaları). Hedef erişimiyle ilgisi yok: admin
// olmayan biri terminale girebilir, admin olan biri rolü yoksa hiçbir
// hedefe giremez. İki eksen bilerek ayrık.
Admin bool
}
User, bastion'da kimliği doğrulanmış kişi.