# Softwarepakke

## Hvad det er

En softwarepakke er den form, software rejser i fra dem, der skriver den, til de maskiner, der kører
den: selve koden som et arkiv eller en mappe sammen med et manifest, der oplyser navn, version og
hvad pakken ellers skal bruge for at virke. I daglig tale siger man bare pakke, i bestemt form
pakken og i flertal pakker og pakkerne. Hvert økosystem trækker grænsen sit eget sted. I npm er en
pakke en fil eller mappe, som en package.json beskriver, og npm's dokumentation skelner den fra et
modul, som er alt, hvad Node.js kan indlæse med require() eller import; kun de moduler, der har en
package.json, er pakker. Python bruger ordet om to ting, som
[Python Packaging User Guide](https://packaging.python.org/) holder
adskilt: en importpakke er en mappe med moduler, som koden importerer, mens en distributionspakke er
det versionerede arkiv, brugeren henter og installerer. I Rust samler en pakke en eller flere crates
med en Cargo.toml, der fortæller, hvordan de bygges. På Debian er enheden den binære pakke,
.deb-filen, som systemet installerer og holder styr på.

En pakke er en distributionsenhed, og den er lettest at forklare ud fra naboerne. Et bibliotek er
kode; en pakke er måden, koden sendes ud på, og det samme bibliotek kan nå sine brugere som
npm-pakke, Python-wheel og Debian-pakke på samme tid. En pakkehåndtering er det program, der læser
manifesterne, henter det, de nævner, og installerer det hele i en rækkefølge, der går op: npm, pip,
Cargo og apt. Et register eller pakkearkiv er stedet, hvor pakkerne ligger klar, som npm-registret,
PyPI, crates.io eller et Debian-spejl. Sprogenes egne pakkehåndteringer installerer kode til ét
projekt, som regel i en mappe ved siden af det. Systemets pakkehåndtering installerer software til
hele maskinen og noterer, hvilken pakke der har lagt hver fil hvor.

## Hvorfor det betyder noget

Kode fra andre når et projekt ad en af tre veje: gennem en pakkehåndtering, kopieret ind i
repositoriet med sin licens og en kontrolsum, hvilket kaldes vendoring, eller bygget fra kildekoden.
Pakkehåndteringen er det sædvanlige valg, og med god grund. Den løser hele træet af afhængigheder,
opdaterer det med én kommando og noterer, hvad den installerede. Prisen er tillid: projektet stoler
nu på hvert manifest i træet og på hvert register, træet kom fra. Vendoring bytter den tillid for
arbejde, for den kopierede kode ændrer sig kun, når nogen bevidst kopierer en ny version ind. At
bygge fra kildekoden giver mest kontrol og kræver mest vedligehold.

Det er i manifestet, de fleste afvejninger viser sig. Det angiver som regel et interval af versioner
frem for én bestemt; i npm godtager et caret-interval som ^10.0.1 enhver senere udgivelse med samme
hovedversion. Intervaller lader rettelser komme ind, uden at nogen redigerer filen, men de betyder
også, at to installationer med en uges mellemrum kan give to forskellige træer. En lockfil lukker
det hul. npm skriver package-lock.json med det præcise træ, den byggede, så senere installationer
gentager det, Cargo skriver Cargo.lock med den præcise version af hver afhængighed, og begge regner
med, at filen bliver committet. Manifestet siger, hvad projektet vil acceptere; lockfilen siger,
hvad det faktisk fik.

Manifester siger også, hvor meget én pakke har brug for en anden. Debian Policy Manual opstiller en
rangstige. Depends er absolut: pakken bliver først konfigureret, når alt, hvad den afhænger af, er
konfigureret. Recommends er stærk, men ikke absolut, og gælder pakker, der hører med i alle andre
end usædvanlige installationer. Suggests nævner pakker, der gør den mere nyttig, mens en
installation uden dem er helt rimelig. En installation uden en anbefalet pakke er gyldig, og
softwaren i den kan stadig have brug for pakken til en af sine opgaver.

Alle værktøjer, der ræsonnerer om pakker, stoler på deres metadata. En licenskontrol læser den
erklærede licens, ofte et SPDX-udtryk som MIT OR Apache-2.0, hvor OR giver et valg, og AND kræver
alle licenserne på én gang. En sårbarhedsscanner sammenholder erklærede navne og versioner med
offentliggjorte sikkerhedsadvarsler. Er en erklæring forkert, tager hvert værktøj, der læser den,
fejl på samme måde, og derfor fortjener licensfilerne i en pakke, der sendes ud med et produkt, at
blive læst for sig.

## Sådan ser det ud i praksis

En hærdningsgennemgang af en produktionsserver viste, at dens jail, der skulle udelukke angribere,
aldrig havde udelukket nogen, og en af tre separate årsager var en relation mellem pakker. Til selve
udelukkelsen brugte jailen et eksternt firewallværktøj, som pakkernes erklæringer kun regner for
anbefalet og ikke for påkrævet, og serveren var bygget uden det. Jailen startede, talte mislykkede
logins og nåede frem til sin beslutning hver gang, og først ved det sidste skridt, selve
blokeringen, gik den i stå.
[Gennemgangen af tre SSH-beskyttelser](/da/portfolio/found-three-ssh-brute-force-protections-that-never-worked-110/)
opdagede det ved at se på det kørende system i stedet for konfigurationsfilerne og efterlod en regel
om, at enhver sikkerhedskontrol bakkes op af en automatisk assertion og ikke af en kommentar.

En Rust-kerne, der linkes ind i en udgivet app, tager hele sin afhængighedsgraf med ind i produktet,
her 644 crates, hver med sin egen licenserklæring. En afhængighed flere lag nede kan skifte til en
copyleft-licens fra én version til den næste, og det eneste spor ville være en ændret linje i en
lockfil, som få læser.
[Licensauditten af 644 crates](/da/portfolio/audited-644-rust-crates-for-licence-compatibility-163/)
kører ved hvert build: den holder hver crates licensudtryk op mod en godkendelsesliste, lader et
valg mellem licenser passere, når én af dem er godkendt, og en kombination kun, når de alle er, og
stopper buildet ved alt andet. For at vise, at tjekket kan slå til, blev det med vilje brudt to
gange, én gang ved at omskrive en crates licensudtryk og én gang ved at flytte den fastlåste
kernerevision uden at generere auditten igen. Siden er klar om grænsen: auditten læser erklærede
metadata, som selv kan være forkerte, og den er ikke juridisk rådgivning.

Virksomhedens egen git-forge blev
[hentet som pakke fra leverandørens eget repository](/da/portfolio/deployed-the-companys-own-git-forge-121/)
og kører som ét program, styresystemet holder i gang. At føje en leverandørs repository til en
server er en beslutning for sig, for fra da af installerer de almindelige opdateringer alt, hvad den
leverandør udgiver og signerer.

Selve sitet behøver ikke Node for at køre. Dets package.json er markeret som privat, hvilket får npm
til at nægte at udgive den, rummer kun udviklingsværktøjer med caret-intervaller og ligger ved siden
af en committet package-lock.json. Dets pyproject.toml indstiller en linter, en stavekontrol og en
typekontrol og har hverken en [project]- eller en [build-system]-tabel, fordi filen ikke beskriver
en pakke, der skal bygges. Ikonerne er vendoret: kopieret ind sammen med den oprindelige licensfil
og en SHA-256-kontrolsum for hver fil.

## Hvor det går galt

### En funktion melder alt vel og udfører intet

En tjeneste starter, logger, at den kører, og springer stille en af sine opgaver over hver gang. Et
program, den kalder, hører til en anbefalet eller foreslået pakke, som installationen havde lov til
at udelade. Når en funktion afhænger af et eksternt program, så tjek ved opstart, at programmet
findes, og stop med en tydelig fejl, hvis det mangler, erklær det som et hårdt krav i jeres egen
udrulning, og test funktionen hele vejen igennem på en server, der er bygget som produktionen.

### To maskiner installerer forskellig kode fra samme manifest

Buildet går igennem på en bærbar og fejler i den kontinuerlige integration, eller omvendt, uden at
projektets egen kode er ændret. Manifestet angiver versionsintervaller, lockfilen blev aldrig
committet, og en afhængighed udgav en ny version mellem de to installationer. Commit lockfilen,
installér fra den i automatiseringen med kommandoer, der nægter at ændre den, som npm ci eller cargo
build --locked, og opdatér den som en selvstændig ændring, som nogen gennemgår.

### En licens skifter inde i en velkendt afhængighed

Et produkt, der bestod en licensgennemgang sidste år, sender nu kode ud under vilkår, ingen har sagt
ja til, mens listen over afhængigheder ser ud som før. En eksisterende afhængighed skiftede licens
mellem to versioner, og en rutineopdatering trak den nye version ind via lockfilen. Tjek de
erklærede licenser mod en eksplicit godkendelsesliste ved hvert build, så skiftet stopper buildet
samme dag, det dukker op.

## Læs mere

- [npm Docs: About packages and modules](https://docs.npmjs.com/about-packages-and-modules) — npm's
  egen definition af en pakke, og hvorfor et modul ikke altid er en.
- [Debian Policy Manual: Declaring relationships between packages](https://www.debian.org/doc/debian-policy/ch-relationships.html)
  — Depends, Recommends, Suggests og de øvrige felter, og hvad en pakkehåndtering gør med hvert af
  dem.
- [Python Packaging User Guide: Writing your pyproject.toml](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/)
  — de tre tabeller i pyproject.toml og de metadata, en Python-distributionspakke erklærer.
- [The Cargo Book: Cargo.toml vs Cargo.lock](https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html)
  — hvorfor manifestet beskriver, hvad et projekt accepterer, mens lockfilen noterer præcise
  versioner, og hvornår den skal committes.
- [SPDX Specification 2.3.1: License expressions](https://spdx.github.io/spdx-spec/v2.3.1/SPDX-license-expressions/)
  — syntaksen, pakker bruger til at erklære én licens, et valg mellem flere eller et sæt, der gælder
  samtidig.

<https://engineer.company/da/glossary/package/>
