Zum Inhalt springen
S3: eine Freigabe ist ein Bucket

S3 – eine Freigabe ist ein Bucket

S3 über den Freigabebaum pro Benutzer: SigV4-Authentifizierung, was bereitgestellt wird, was abgelehnt wird, und seine Grenzen.

serve "s3" { addr = "0.0.0.0:9000" }

Oder über HTTPS, seit v0.9.0, mit tls = true und einem tls-Block:

serve "s3" {
  addr = "0.0.0.0:9000"
  tls  = true
}

Das fünfte Protokoll, und dasjenige, das ein Image von allem aus erreichbar macht, was Objektspeicher spricht – ein Backup-Werkzeug, eine Datenpipeline, rclone, das fetch() eines Browsers. Nichts wird eingehängt.

GET /                      the shares this person may use, as buckets
GET /photos?list-type=2    the files in that share, as keys
GET /photos/holiday.jpg    the file itself, Range and all

Eine Freigabe ist ein Bucket

Bereitgestellt über denselben Baum pro Benutzer, den SFTP verwendet: ein Dateisystem, dessen oberste Ebene aus den Freigaben besteht, die diese Person nutzen darf. Eine Freigabe, die alice nicht nutzen darf, ist also kein Bucket, den alice sehen kann, und die Zugriffsregeln werden an einer Stelle angewendet, statt in ein zweites Protokoll kopiert zu werden.

Ein Verzeichnis innerhalb einer Freigabe ist ein Präfix, kein Objekt

Genau das nehmen S3-Clients ohnehin an – sie zeigen Präfixe, die auf das Trennzeichen enden, als Ordner an. Ein GET auf ein Verzeichnis antwortet mit NoSuchKey, weil eine Antwort 200 einen Client glauben ließe, er könne die Bytes eines Ordners abrufen.

Ein Access Key ist ein Benutzer; der Secret Key ist sein Passwort

export AWS_ACCESS_KEY_ID=alice
export AWS_SECRET_ACCESS_KEY=$(cat alice.pw)
aws --endpoint-url http://attic.example.org:9000 s3 ls

SigV4 weist den Besitz des Geheimnisses nach, indem daraus ein HMAC berechnet wird – das Passwort geht nie über die Leitung, genau wie bei NTLMv2.

Daher muss das Verzeichnis das Passwort HALTEN, nicht nur prüfen

Eine Quelle, die ein Passwort nur verifizieren kann – ein LDAP-Bind, eine bcrypt-Spalte –, kann WebDAV beantworten und kann S3 nicht beantworten.

Und auch kein NT-Hash: Das ist der MD4-Wert, mit dem SMB arbeiten kann, und ein HMAC lässt sich daraus nicht berechnen. Eine Identität, die nur einen NT-Hash hält, bedient SMB und nicht S3 – nahe Nachbarn statt derselben Antwort. check gibt die Spalte aus.

⛔ Es gibt nirgends im Programm einen Password()-Accessor: Das Passwort wird nur über Identity.Derive erreicht, das eine Ableitung entgegennimmt und deren Ergebnis zurückgibt. Für S3 ist die Ableitung die Identität, weil der Schlüsselplan von SigV4 in go-filesystems/s3 liegt; das Geheimnis geht an diese Signaturprüfung für diesen einen Benutzer und nirgendwo sonst hin.

Was bereitgestellt wird

ListBuckets, HeadBucket, ListObjectsV2 mit Präfix, Trennzeichen, max-keys und Fortsetzung, HeadObject und GetObject mit Range. SigV4 in beiden Formen, im Header und vorsigniert.

Die Objekt-API selbst ist go-filesystems/s3.

Was abgelehnt wird, und warum

Schreibvorgänge antworten mit 403

PUT, POST und DELETE werden mit AccessDenied („this export is read-only“) abgelehnt. fileshare hält den S3-Server schreibgeschützt, weil eine Freigabe, die eine Person nur lesen darf, an derselben Stelle ablehnen muss wie bei SFTP, und das ist noch nicht verdrahtet; go-filesystems/s3 v0.3.0, auf dem dieses Release aufbaut, lehnt ohnehin jeden Schreibvorgang ab. Ablehnen ist besser als ein halb geschriebenes Objekt.

Ein Bearer-Token wird hier nicht akzeptiert

OIDC funktioniert über WebDAV und nirgendwo sonst. S3 authentifiziert mit einer SigV4-Signatur – einem HMAC über die Anfrage, ohne ein Feld, in das ein Bearer-Token passt.

Der übliche Weg ist STS AssumeRoleWithWebIdentity, das ein OIDC-Token gegen temporäre Zugangsdaten tauscht, mit denen der Client dann signiert. Das ist ein anderer Mechanismus, und er ist nicht implementiert.

Multipart-Upload, Versionierung, ACLs, Richtlinien, Lifecycle-Regeln, Replikation und Tagging sind nicht implementiert. Eines davon zu setzen oder einen Multipart-Upload zu beginnen, ist ein Schreibvorgang und antwortet wie jeder andere mit AccessDenied. Ein GET, das nach einem davon fragt (?versioning, ?acl, ?tagging…), wird nicht unterschieden: Die Query wird ignoriert, und das Listing des Buckets bzw. das Objekt wird zurückgegeben. NotImplemented beantwortet ein anderes Listing als list-type=2, jede Methode außer GET auf / und eine Methode, die keine Operation hier verwendet.

Zwei Grenzen, die man kennen sollte, bevor man es auf etwas Großes richtet

GetObject liest das ganze Objekt in den Arbeitsspeicher. Die Treiberschnittstelle bietet ReadFile und sonst nichts – keinen Opener, keinen Reader –, daher gibt es kein streamendes Lesen, das man nutzen könnte. Eine Obergrenze von 256 MiB antwortet mit EntityTooLarge, statt den Prozess zum Absturz zu bringen.

LastModified ist die Epoche. filesystem.Stat trägt Mode, Size und Inode und überhaupt keine Änderungszeit. Das Feld ist im S3-Schema nicht optional, also muss etwas gesendet werden; die Epoche ist offensichtlich keine echte Zeit, während time.Now() wie eine gerade geänderte Datei aussähe und jedes Sync-Werkzeug bei jedem Lauf alles kopieren ließe.

Erstellt am von tannevaled

Geändert am von tannevaled