Saltar al contenido
S3: un recurso compartido es un bucket

S3 — un recurso compartido es un bucket

S3 sobre el árbol de recursos compartidos por usuario: autenticación SigV4, lo que se sirve, lo que se rechaza, y sus límites.

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

O por HTTPS, desde la v0.9.0, con tls = true y un bloque tls:

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

El quinto protocolo, y el que hace que una imagen sea accesible desde cualquier cosa que hable almacenamiento de objetos: una herramienta de copias de seguridad, una canalización de datos, rclone, el fetch() de un navegador. No se monta nada.

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

Un recurso compartido es un bucket

Servido sobre el mismo árbol por usuario que usa SFTP: un sistema de archivos cuyo nivel superior son los recursos compartidos que esa persona puede usar. Así, un recurso compartido que alice no puede usar no es un bucket que alice pueda ver, y las reglas de acceso se aplican en un solo lugar en vez de copiarse a un segundo protocolo.

Un directorio dentro de un recurso compartido es un prefijo, no un objeto

Que es lo que los clientes S3 ya creen: muestran como carpetas los prefijos que terminan en el delimitador. Un GET de un directorio responde NoSuchKey, porque responder 200 haría creer a un cliente que puede descargar los bytes de una carpeta.

Una clave de acceso es un usuario; la clave secreta es su contraseña

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 prueba la posesión del secreto calculando un HMAC a partir de él: la contraseña nunca cruza la red, exactamente como con NTLMv2.

Por eso el directorio debe GUARDAR la contraseña, y no solo comprobarla

Un origen que solo puede verificar una contraseña —un bind LDAP, una columna bcrypt— puede responder a WebDAV y no puede responder a S3.

Y tampoco sirve un hash NT: es el MD4 con el que SMB puede trabajar, y a partir de él no se puede calcular un HMAC. Una identidad que solo guarda un hash NT sirve SMB y no S3: vecinos cercanos, pero no la misma respuesta. check imprime la columna.

⛔ No existe ningún accesor Password() en todo el programa: a la contraseña solo se llega mediante Identity.Derive, que recibe una derivación y devuelve su resultado. Para S3 la derivación es la identidad, porque el cálculo de claves de SigV4 vive en go-filesystems/s3; el secreto va a esa comprobación de firma, para ese único usuario, y a ningún otro sitio.

Lo que se sirve

ListBuckets, HeadBucket, ListObjectsV2 con prefijo, delimitador, max-keys y continuación, HeadObject, y GetObject con Range. SigV4 en sus dos formas, en cabecera y prefirmada.

La API de objetos en sí es go-filesystems/s3.

Lo que se rechaza, y por qué

Las escrituras responden 403

PUT, POST y DELETE se rechazan con AccessDenied (“this export is read-only”). fileshare mantiene el servidor S3 en solo lectura porque un recurso compartido que una persona solo puede leer tiene que rechazar en el mismo punto en que lo hace SFTP, y eso aún no está conectado; go-filesystems/s3 v0.3.0, sobre la que se construye esta versión, rechaza de todos modos cualquier escritura. Rechazar es mejor que un objeto escrito a medias.

Aquí no se acepta un token bearer

OIDC funciona por WebDAV y en ningún otro sitio. S3 autentica con una firma SigV4 —un HMAC sobre la petición—, sin ningún campo en el que quepa un token bearer.

La vía habitual es STS AssumeRoleWithWebIdentity, que canjea un token OIDC por credenciales temporales con las que luego firma el cliente. Es un mecanismo distinto, y no está implementado.

La subida multiparte, el versionado, las ACL, las políticas, las reglas de ciclo de vida, la replicación y el etiquetado no están implementados. Configurar cualquiera de ellos, e iniciar una subida multiparte, es una escritura, y responde AccessDenied como cualquier otra. Un GET que pide uno de ellos (?versioning, ?acl, ?tagging…) no se distingue: la consulta se ignora y se devuelve el listado del bucket o el objeto. NotImplemented responde a un listado distinto de list-type=2, a cualquier método salvo GET sobre /, y a un método que ninguna operación de aquí utiliza.

Dos límites que conviene conocer antes de apuntarlo a algo grande

GetObject lee el objeto entero en memoria. La interfaz del controlador ofrece ReadFile y nada más —ni función de apertura, ni lector—, así que no hay lectura en streaming que usar. Un límite, 256 MiB, responde EntityTooLarge en lugar de tumbar el proceso.

LastModified es la época Unix. filesystem.Stat lleva Mode, Size e Inode, y ninguna fecha de modificación. El campo no es opcional en el esquema de S3, así que hay que enviar algo; la época es obviamente una fecha que no es real, mientras que time.Now() parecería un archivo que acaba de cambiar y haría que cualquier herramienta de sincronización lo copiara todo, en cada ejecución.

Creada el por tannevaled

Modificada el por tannevaled