dapla-toolbelt-pseudo
dapla-toolbelt-pseudo er en python-pakke som har som sitt hovedformål å gi Dapla-brukere muligheten til å pseudonymisere, de-pseudonymisere og re-pseudonymisere data. Det skal sikre at brukerne av Dapla har verktøyene de trenger for å jobbe med direkte identifiserende opplysninger i henhold til lovverk og SSBs tolkninger av disse.
Siden tilgang til direkte identifiserende opplysninger er underlagt strenge regler, så krever bruken av dapla-pseudo-toolbelt at man forholder seg til vedtatte standarder som datatilstander og systemer som Kildomaten. I tillegg er det en streng tilgangsstyring til hvor man kan kalle funksjonaliteten fra. Tjenestene er satt opp på en slik måte at Dapla-team skal være selvbetjent i bruken av funksjonaliteten, samtidig som regler, prosesser og standarder etterleves på enklest mulig måte.
I SSB er det bestemt at all data skal klassifiseres på en standardisert måte basert på datatilstander for å avgjøre om de er sensitive, skjermet eller åpen. Den eneste datatilstanden som klassifiseres som sensitiv er kildedata. Det er derfor bestemt at pseudonymisering er en av prosesseringene som skal skje mellom datatilstandene kildedata og inndata.
Forberedelser
Før man tar i bruk funksjonaliteten er det viktig at man kjenner godt til tilgangstyring i Dapla-team og Kildomaten, og har diskutert med seksjonen hvordan man skal behandle direkte identifiserende opplysninger i de aktuelle dataene.
For at et Dapla-team skal kunne bruke dapla-toolbelt-pseudo må Kildomaten være skrudd på for miljøet1 man ønsker å jobbe fra. Som standard får alle statistikkteam skrudd på Kildomaten i prod-miljøet og ikke i test-miljøet. Ønsker du å aktivere Kildomaten i test-miljøet kan dette gjøres selvbetjent som en feature.
Tilgangsstyring
Tilgang til å funksjonalitet i dapla-toolbelt-pseudo kan regnes som sensitivt i seg selv, og derfor er det en streng tilgangsstyring for bruk av tjenesten. I prod-miljøet kan man kun ta i bruk funksjonaliteten ved å prosessere dataene i Kildomaten, og det er bare tilgangsgruppen data-admins som har tilgang til å godkjenne slike automatiske prosesseringer. I test-miljøet derimot kan alle på teamet benytte seg av all funksjonalitet, siden det aldri skal forekomme ekte data her.
| Aktør | Validator | Pseudonymize() | Depseudonymize() | Repseudonymize() |
|---|---|---|---|---|
| Kildomaten | ✅ | ✅ | ✅ | ✅ |
| data-admins (interaktivt) | ✅ | ✅ | ✅ | ✅ |
| developers (interaktivt) | ✅ | ✅ | ✅ | ✅ |
| Aktør | Validator | Pseudonymize() | Depseudonymize() | Repseudonymize() |
|---|---|---|---|---|
| Kildomaten | ✅ | ✅ | 🚫 | 🚫 |
| data-admins (interaktivt) | 🚫 | 🚫 | 🚫 | 🚫 |
| developers (interaktivt) | 🚫 | 🚫 | 🚫 | 🚫 |
I Tabell 1 ser vi fra Tabell 1 (a) at man i test-miljøet har full tilgang til funksjonaliteten i dapla-toolbelt-pseudo, både fra Kildomaten og når man jobber interaktivt2 i Jupyterlab. Tabell 1 (b) viser at det kun er tilgang til pseudonymize() og validator() fra Kildomaten i prod-miljøet, og man kan aldri interaktivt kan kalle på funksjoner som potensielt avslører et pseudonym. Av den grunn er det alltid anbefalt å teste ut koden sin i test-miljøet før den produksjonssettes i i prod-miljøet med Kildomaten.
Selv om man har videre rettigheter til å bruke funksjonaliteten i dapla-toolbelt-pseudo fra test-miljøet sammenlignet med prod-miljøet, så betyr ikke det at samme input i de to miljøene vil samme output. Når funksjonaliteten kalles fra test-miljøet så benyttes det automatisk en annen krypteringsnøkkel enn den som benyttes i prod. På den måten vil et pseudonym produsert fra prod-miljøet aldri være likt det som produseres fra prod-miljøet selv om input skulle være den samme.
Funksjonalitet
I denne delen viser vi hvilken funksjonalitet som tilbys gjennom dapla-toolbelt-pseudo. Eksempelkoden under viser hvordan man ville kjørt det fra en notebook i test-miljøet til teamet, og ikke hvordan koden må skrives når det skal kjøres i Kildomaten3.
Installering
dapla-toolbelt-pseudo er ferdig installert i Kildomaten. Men ønsker du å bruke den i test-miljøet til teamet så kan du installere det i et ssb-project fra PyPI med denne kommandoen:
Terminal
poetry add dapla-toolbelt-pseudoDataformater
dapla-toolbelt-pseudo støtter innlesing av følgende dataformater:
- CSV
- JSON
- Pandas dataframes
- Polars dataframes
Eksemplene under viser hovedskelig innlesing av dataframes fra minnet4, men du kan lese mer om filbasert prosessering lenger ned i kapitlet.
Pseudonymisering
Pseudonymisering tilbys via Pseudonymize-metoden i dapla-toolbelt-pseudo. Den følger et builder-pattern der man spesifiserer hva og i hvilken rekkefølge operasjonene skal gjøres. Anta at det finnes en Polars dataframe i minnet som heter df hvor kolonnen fnr skal pseudonymiseres. Da vil koden se slik ut:
Notebook
from dapla_pseudo import Pseudonymize
result_df = (
Pseudonymize.from_polars(df)
.on_fields("fnr")
.with_default_encryption()
.run()
.to_polars()
)I koden over så angir from_polars(df) at kolonnen vi ønsker å pseudonymisere ligger i en Polars dataframe i minnet og heter df. Deretter spesifiserer vi at kolonnen fnr er den som skal behandles med funksjonen on_fields("fnr"). Videre angir with_default_encryption() at fnr skal pseudonymiseres med dapla-toolbelt-speudo sin standard-algoritme5. Til slutt ber vi om at det ovennevnte blir kjørt med funksjonen run(), og at dataene skal returneres som en Polars dataframe med funksjonen to_polars().
Se flere eksempler i dokumentasjonen.
De-pseudonymisering
De-pseudonymisering tilbys via Depseudonymize-metoden i dapla-toolbelt-pseudo. Den følger et builder-pattern der man spesifiserer hva og i hvilken rekkefølge operasjonene skal gjøres. Anta at det finnes i en Polars dataframe i minnet som heter df hvor kolonnen fnr skal de-pseudonymiseres. Da vil koden se slik ut:
Notebook
result_df = (
Depseudonymize.from_polars(df)
.on_fields("fnr")
.with_default_encryption()
.run()
.to_polars()
)Oppbygningen av koden med Depseudonymize() er helt lik som for Pseudonomize(). Les beskrivelsen der for å se hva de ulike funksjonskallene gjør. Se flere eksempler i dokumentasjonen.
De-pseudonymisering er også støttet for informasjon som først er transformert til stabil ID og deretter pseudonymisert med Papis-nøkkelen. I disse tilfellene kan det også være behov for å spesifisere hvilken versjon av snr-katalogen man ønsker å benytte for å erstatte snr med fødselsnummer:
Notebook
from dapla_pseudo import Depseudonymize
result_df = (
Depseudonymize.from_pandas(df)
.on_fields("fnr")
.with_stable_id(
sid_snapshot_date="2023-05-29")
.run()
.to_pandas()
)I eksempelet over spesifiserer vi datoen 2023-05-29 og da benyttes snr-katalogen som ligger nærmest i tid til denne datoen. Hvis sid_snapshot_date ikke oppgis benyttes siste tilgjengelige versjon av katalogen.
Foreløpig er det kun tilgang til å pseudonymisere i test-miljøet med test-data. Ta kontakt med Dapla-teamene dersom det dukker opp behov for å kunne de-pseudonymisere i prod-miljøet.
Re-pseudonymisering
Under utvikling.
Stabil ID
I statistikkproduksjon og forskning er det viktig å kunne følge de samme personene over tid. Derfor har fødselsnummer ofte blitt oversatt til en mer stabilt identifikator, ofte kalt SNR eller stabil ID6. Funksjonene with_stable_id() og validator() bruker stabilID-katalogen til å henholdsvis bytte ut fødselsnummer med stabil ID, og for å validere om fødselsnummer er gyldige(se under).
Du kan selv spesifisere hvilken versjon av SNR-katalogen du ønsker å bruke. Det gjør du ved å oppgi en gyldighetsdato og så finner dapla-toolbelt-pseudo hvilken versjon av katalogen som ligger nærmest i tid.
Papis-pseudonym
Dapla tilbyr samme pseudonym som Papis-prosjektet7. Denne kan benyttes på 2 måter:
- Pseudonymisere hvilken som helst informasjon med samme nøkkel som Papis.
- Transformere fødselsnummer til snr-nummer og deretter pseudonymisere med samme nøkkel som Papis.
Punkt 1 er nyttig for de som har pseudonymisert informasjon på bakken tidligere og vil ha samme pseudonym på Dapla8. Dette kan gjelde hvilken som helst informasjon, også direkte pseudonymisering av fødselsnummer, uten at det er gått via snr-nummer. Her er et eksempel på hvordan man pseudonymiserer på denne måten:
Notebook
result = (
Pseudonymize.from_pandas(df)
.on_fields("fornavn")
.with_papis_compatible_encryption()
.run()
.to_pandas()
)Punkt 2 over er nok den som benyttes mest i SSB siden den sikrer at pseudonymisert fødselsnummer kan kobles med data som er pseudonymisert på bakken. Her er et eksempel på hvordan man pseudonymiserer snr med Papis-nøkkelen:
Notebook
result = (
Pseudonymize.from_pandas(df)
.on_fields("fnr")
.with_stable_id()
.run()
.to_pandas()
)At Papis og Dapla tilbyr samme pseudonum betyr egentlig at vi bruker samme krypteringsalgoritme, og en felles krypteringsnøkkel. Krypteringsalgoritmen som benyttes er formatpreserverende (FPE) og biblioteket som brukes er Tink FPE Python. En begrensning med algorimen er at kun karakterer som finnes i et forhåndsdefinert karaktersett (tall, store og små bokstaver fra a-z) blir vurdert. Andre karakterer f. eks æøå blir ikke kryptert. Papis-nøkkelen (som brukes f. eks for fnr og snr) benytter en SKIP-strategi for karakterer som faller utenom, som betyr at algoritmen simpelthen “hopper over” disse. FPE-algoritmen er også avhengig av størrelsen på det predefinterte karaktersettet for å avgjøre minimumslengden på teksten som pseudonymiseres. For Papis-nøkkelen betyr det at teksten minst må være 4 karakterer lang. Kortere tekster blir ikke kryptert.
Validere fødselsnummer
Validator-metoden kan benyttes til å sjekke om fødselsnummer finnes i SNR-katalogen (se over), og returnerer de ugyldige fødselsnummerne tilbake. Her kan man også spesifisere hvilken versjon av SNR-katalogen man ønsker å bruke. Standard, hvis ingenting velges, er at siste tilgjengelige versjon velges. Under er et eksempel på hvordan man validerer fødselsnummer for en gitt gyldighetsdato:
Notebook
from dapla_pseudo import Validator
result = (
Validator.from_polars(df)
.on_field("fnr")
.validate_map_to_stable_id(
sid_snapshot_date="2023-08-29"
)
)
# Vis hvilken versjon av SNR-katalogen som er benyttet
result.metadata
# Vis fødselsnummer som ikke fikk treff i SNR-katalogen som en Polars dataframe
result.to_polars()
# Eventuelt som en Pandas DataFrame
result.to_pandas()Se flere eksempler i dokumentasjonen.
Wildcards
Kommer snart.
Dataminimering
Kommer snart.
Algoritmer
Følgende sammentsetninger av algoritmer, nøkler og SID-mapping støttes på Dapla:
| Algoritme | dapla-toolbelt-pseudo | Nøkkelnavn | Støtter SID-mapping |
|---|---|---|---|
| TINK-DAED | with_default_encryption |
ssb-common-key-1 | Nei |
| TINK-DAED | with_default_encryption |
ssb-common-key-2 | Nei |
| TINK-FPE | with_stable_id |
papis-common-key-1 | Ja |
| TINK-FPE | with_papis_compatible_encryption |
papis-common-key-1 | Nei |
Metadata
Det genereres to typer av metadata når man pseudonymiserer:
- Datadoc
- Metadata
De to typene av metadata returneres til brukeren i to forskjellige objekter.
Datadoc
Datadoc-metadata er på et format som er integrert i Datadoc9. I koden til høyre så printes metadataene fra et kall til Pseudonymize ved å skrive print(result.datadoc). Da printer man objektet interaktivt i f.eks. Jupyter. Merk at dette kun er mulig i test-miljøet
Det er anbefalt å ta vare på Datadoc-metadataen i Kildomaten. Da er det lettere å skrive filen direkte i bøtta med to_file()-funksjonen, også i kodesnutten til høyre. to_file("<filsti>.parquet") skriver to filer til filstien - en fil med de pseudonymiserte dataene, og en Datadoc-fil med samme filnavn, men da med endelsen __DOC på slutten av filnavnet. Med denne funksjonen slipper man å tenke på om filen skrives med riktig formattering.
Når man kjører pseudonymisering fra Kildomaten er det viktig å tenke på at felter som er pseudonymisert ikke må endres uten at metadataene også endrer. Da kan man risikere at metadatene ikke lenger beskriver riktig informasjon.
Under ser man hvilken informasjon som genereres fra pseudonymiseringen.
Datadoc
[
{
"short_name": "fnr",
"data_element_path": "fnr",
"pseudonymization": {
"stable_identifier_type": "FREG_SNR",
"stable_identifier_version": "2023-08-31",
"encryption_algorithm": "TINK-FPE",
"encryption_key_reference": "papis-common-key-1",
"encryption_algorithm_parameters": [
{
"keyId": "papis-common-key-1"
},
{
"snapshotDate": "2023-08-31"
},
{
"strategy": "skip"
}
]
}
}
]Datadoc-metadataen er en liste av pseudonymiserte variabler. Av metadatene kan vi i dette tilfellet se at det kun er ett element i listen - med andre ord at det bare var feltet fnr som ble pseudonymisert. Vi ser også av stable_identifier_type at fnr ble oversatt til snr, og at versjonen av SNR-katalogen var fra 2023-08-31. encryption_algorithm angir at det var det var den formatpreserverende algoritmen TINK-FPE som ble benyttet. keyID: "papis-common-key-1" angir hvilken nøkkel-id som ble benyttet. strategy: "SKIP" refererer til at den format-preserverende algoritmen skal “hoppe over” ugyldige karakterer og la de være som de er.
Denne informasjonen vil være svært nyttig i SSB hvis man senere skal kunne de-pseudonymisere eller re-pseudonymisere data.
Legge til pseudonymiseringsmetadata for allerede eksisterende Datadoc-metadata
Hvis man allerede har Datadoc-metadata for et datasett, og man ønsker å legge til pseudonymiseringsmetadata, kan man gjøre dette ved å bruke with_metadata()-metoden.
with_metadata(datadoc_metadata) tar et Datadoc-objekt som et argument. Datadoc-objektet for et datasett fås ved hjelp av dapla-toolbelt-metadata. Les mer om pakken her.
Datadoc-metadata
result = (
Pseudonymize.from_pandas(df)
.with_metadata(datadoc_obj)
.on_fields("fnr")
.with_stable_id()
.run()
)
result.to_file("gs://somebucket/somefile.parquet") # NB: Husk å skrive tilbake data og metadata!Metadata
Den andre typen av metadata kan hentes ut etter et kall til Pseudonymize med kommandoen result.metadata. Den returnerer en python dictionary. Den inneholder hovedsaklig logginformasjon og metrikker foreløpig. For de som pseudonymiserer med with_stable_id() kan output se slik ut:
Metadata
{
'fnr': {
'logs': [
'No SID-mapping found for fnr 999999*****',
'No SID-mapping found for fnr XX',
'No SID-mapping found for fnr X8b7k2*'
],
'metrics': [
{'MAPPED_SID': 10},
{'MISSING_SID': 3}
]
}
}I Kildomaten kan det vært nyttig å printe denne informasjonen til loggene. Av eksempelet over ser vi at verdier som er over 6 karakterer lange blir maskert.
Prosessering av store datasett
Når man pseudonymiserer med Pandas eller Polars DataFrames må hele datasettet leses inn i minnet for at det skal kunne pseudonymiseres. Det kan være problematisk hvis datasettet er stort, fortrinnsvis i Kildomaten. For å løse dette problemet tilbyr dapla-toolbelt-pseudo muligheten til å strømme alle kolonner i datasettet som ikke skal pseudonymiseres - upseudonymiserte kolonner blir da delt opp i mindre biter (“chunks”) når den nye filen skrives til bøttene. Man trenger da kun å holde de pseudonymiserte kolonnene i minnet, noe som kan betydelig redusere minnebruken.
Eksempel for Kildomaten:
Strømming i Kildomaten
import polars as pl
from dapla_pseudo import Pseudonymize
def main(source_file):
# Les inn data som en "lazy" DataFrame
lazy_df = pl.scan_csv(source_file)
result = Pseudonymize.from_polars(lazy_df).on_fields("fnr").with_stable_id().run()
output_file = source_file.replace("kilde", "produkt")
result.to_file(output_file)Funksjonaliteten lener seg på Polars’ LazyFrames. I stedet for pl.read_csv() som leser hele datasettet inn i minnet, bruker man pl.scan_csv() - denne funksjonen leser inn dataene som en “lazy” DataFrame, kalt en LazyFrame.
Når man skriver eller henter ut resultatet av pseudonymiseringen, må man bruke to_file()-funksjonen for å strømme dataen til fil som i eksempelet over, eller result.to_polars_lazy() for å få resultatet som en LazyFrame. Merk at hvis man har flere mellomsteg i koden mellom innlesning og pseudonymisering, kan man måtte tilpasse koden for LazyFrames. Man kan lese mer i Polars-dokumentasjonen her.
Strømming av data støttes ikke med hierarkiske filer.
Pseudonymisering av hierarkisk data
Hierarkisk data er data som har en trestruktur, og som ofte lagres i formater som JSON eller XML. Hierarkisk data ser slik ut:
hierarchical_data.json
[
{ // Person 1
"person_info": {
"fnr": "11854898347",
"fornavn": "Donald",
"etternavn": "Duck"
},
"kjonn": "M",
"fodselsdato": "020995"
},
{ // Person 2
"person_info": {
"fnr": "01839899544",
"fornavn": "Mikke",
"etternavn": "Mus"
},
"kjonn": "M",
"fodselsdato": "060970"
},
]I dette eksempelet er det det nøstede feltet “person_info” som gjør strukturen hierarkisk. Dette står i kontrast til “flate” data, hvor man gjerne kun har en tabellstruktur som i en CSV-fil.
Pseudonymisering av hierarkisk data er noe mer komplisert enn pseudonymisering av flate data, da man må beskrive hvor i den hierarkiske strukturen man skal pseudonymisere. Dette gjøres ved å lage såkalte PseudoRules.
En PseudoRule har hovedsakelig tre komponenter:
- Function: Beskriver enkrypteringsalgoritmen som skal brukes for feltene i
path. Eksempel:map-sid-ff31(sid-snapshot-date=2019-12-31) - Path: En beskrivelse av nøyaktig hvor i den hierarkiske strukturen det skal pseudonymiseres. I eksempelet over vil pathen til
fnrværeperson_info/fnr, og pathen tilkjonnvil enkelt nok værekjonn. Skråstreken bestemmer hvilket nivå i hierarkiet man peker på. - Pattern: Glob-uttrykk som beskriver hvor i den hierarkiske strukturen man skal pseudonymisere. Eksempel:
**/fnr
Hvordan man genererer de tre feltene vil bli beskrevet senere.
Pseudonymisering av hierarkisk data
Typisk vil man ha flere PseudoRules for å kunne pseudonymisere flere felt i den hierarkiske strukturen. Da er det vanlig å samle alle PseudoRules in en felles .json-fil, som kan se noe slik ut for eksempelet over:
pseudo_rules.json
[
{
"func": "map-sid-ff31(sid-snapshot-date=2019-12-31)",
"path": "person_info/fnr",
"pattern": "**/fnr"
},
{
"func": "daead()",
"path": "person_info/fornavn",
"pattern": "**/fornavn"
},
{
"func": "daead()",
"path": "person_info/etternavn",
"pattern": "**/etternavn"
}
]Ved å lese denne JSON-filen sammen med dataene, forstår dapla-toolbelt-pseudo nøyaktig hvilke felter som skal pseudonymiseres, hvor de befinner seg i den hierarkiske strukturen, og hvilken algoritme som skal benyttes for å pseudonymisere hvert felt. Man kan da bruke dapla-toolbelt-pseudo slik for å pseudonymisere:
process_source_data.py
import json
import pandas as pd
from dapla_pseudo import Pseudonymize, PseudoRule
RULES_FILENAME = "gs://<kildebøtte-navn>/pseudo_rules.json"
def main(source_file: str):
with open(RULES_FILENAME, 'r') as rules_file:
pseudo_rule_list = json.load(file)
# Konverter fra Python dictionaries til PseudoRule-objekter
pseudo_rules = [PseudoRule.from_json(rule) for rule in pseudo_rule_list]
df = pd.read_json(source_file) # Eksempel på innlesning av data, tilpass etter behov
result = (
Pseudonymize
.from_pandas(df)
.add_rules(pseudo_rules)
.run(hierarchical=True) # Viktig å spesifisere at dataene er hierarkiske!
)
output_file = source_file.replace("kilde", "produkt").replace(".json", ".parquet")
result.to_file(output_file) # Eksempel på skriving av data, tilpass etter behovPseudoRules - anatomi og oppsett
Som sagt er en PseudoRule en sammensetning av tre komponenter: func, path og pattern. Vi kommer til å se på hvordan man forholder seg til disse feltene når man setter opp PseudoRules for datagrunnlaget sitt
func
func beskriver hvilken algoritme som skal benyttes for å pseudonymisere det aktuelle feltet. I JSON-strukturen over supplerer man en enkel streng som korresponderer til algoritmen. Dette er veldig likt måten man bruker dapla-toolbelt-pseudo ellers - det er bare en annen syntaks. Bruk tabellen under:
| Algoritme | dapla-toolbelt-pseudo (flat) | dapla-toolbelt-pseudo (hierarkisk) | SID-mapping |
|---|---|---|---|
| TINK-DAED | with_default_encryption() |
daead() |
Nei |
| TINK-FPE | with_stable_id() |
map-sid-ff31() |
Ja |
| TINK-FPE | with_papis_compatible_encryption() |
ff31() |
Nei |
| Redact10 | Ikke tilgjengelig | redact() |
Nei |
I tillegg til funksjonene i tabellen over, kan man også sende inn argumenter til funksjonene i strengen. Disse argumentene må være på formatet func(arg=value). For eksempel, for å spesifisere hvilken versjon av SNR-katalogen, bruker man map-sid-ff31(sid-snapshot-date=2019-12-31). Merk at argumentene bruker bindestrek -, i motsetning til funksjonene i flat format som bruker understrek _.
path
En beskrivelse av nøyaktig hvor i den hierarkiske strukturen det skal pseudonymiseres. I eksempelet over vil pathen til fnr være person_info/fnr, og pathen til kjonn vil enkelt nok være kjonn.
pattern
Pattern er et glob-uttrykk som kan brukes til å generere paths for flere felt i den hierarkiske strukturen - fremgangsmåten for dette blir beskrevet under. Glob-uttryk er et mønster som forsøker å finne “matchende” strenger, det vil si strenger som følger mønsteret i glob-uttrykket.
Det viktigste symbolet er *, også kjent som wildcard-symbolet. * kan erstatte hvilket som helst felt i en path på et gitt nivå i hierarkiet. Dobbel-stjernesymbolet ** kan erstatte hvilket som helst felt i en path uavhengig av hierarkinivå. Noen eksempler:
*/fnrvil matche alle strenger på det første nivået i hierarkiet, og søker etterfnrpå det andre nivået. Dette patternet vil for eksempel matcheperson_info/fnrogperson_data/fnr, oganything/fnr, men ikkeperson_info/barn/fnrfordi FNR-et her ligger på det tredje nivået i hierarkiet.person_*/fnrvil matche bådeperson_info/fnrogperson_data/fnr, men ikkesomething/fnr**/fnrvil matche på alle paths som slutter påfnr, uavhengig av hvor i hierarkiet det befinner seg. Det vil for eksempel matcheperson_info/fnr,person_info/barn/fnrogfnr
Hvordan generere PseudoRules
Enkel struktur
For hierarkiske data med en enkel struktur, vil det lønne seg å manuelt skrive PseudoRules i en JSON-fil som i eksempelet over. Disse lagres i kildebøtta, og kan leses inn i Kildomaten. Går man denne ruta, fyller man ut path for feltene man ønsker å pseudonymisere, velger func for hvert felt, og lar pattern = ** (match alle felter).
Kompleks struktur
For mer komplekse datasett med mange felt og/eller dypt hierarki, kan det være tidkrevende å manuelt skrive PseudoRules og spesielt da path for alle relevante felt. I disse tilfellene kan man generere PseudoRules ved å bruke pattern-feltet.
For å generere PseudoRules, trenger man:
Et sett med delvis utfylte
PseudoRules, hvorfuncogpatterner fylt ut.pathskal ikke fylles ut - det er det som skal genereres. For hver PseudoRule, lag etpatternsom matcher feltene man ønsker å pseudonymisere i datasettet. For dette vil man typisk bruke wildcard-karakterene*og**, som beskrevet over over.Dataenes schema. Et schema beskriver strukturen til dataene og alle mulige felter. Eksempel:
Data:
hierarchical_data.json
[
{
"person_info": {
"fnr": "11854898347",
"fornavn": "Donald",
"etternavn": "Duck"
},
"kjonn": "M",
"fodselsdato": "020995"
},
]Schema:
"person_info": Struct(
"fnr": String,
"fornavn": String,
"etternavn": String
),
"kjonn": String,
"fodselsdato": String
Schemaene må være Parquet-schemaer, og blir typisk hentet fra API-definisjonen hos dataleverandør. Ta kontakt med Team Statistikktjenester for hjelp til å hente ut schema for dine data.
Med utgangspunkt i de delvis utfylte PseudoRules og schema, kan man utlede path for alle relevante felt i datasettet ved hjelp av SchemaTraverser()-klassen i dapla-toolbelt-pseudo. Dette kan gjøres i Dapla Lab, i et Poetry prosjekt med dapla-toolbelt-pseudo installert slik:
partial_pseudo_rules.json
[ // Utfylt i Dapla Lab, og lagres i kildebøtta
{
"func": "map-sid-ff31(sid-snapshot-date=2019-12-31)",
"pattern": "**/fnr"
},
{
"func": "daead()",
"pattern": "**/fornavn"
},
{
"func": "daead()",
"pattern": "**/etternavn"
}
]generate_pseudo_rules.py
import json
import gcsfs
import polars as pl
from dapla_pseudo import PseudoRule
from dapla_pseudo import SchemaTraverser
from pydantic_core import to_jsonable_python
schema_path = "/buckets/kilde/someschema.parquet"
# Leser schema:
schema = pl.read_parquet_schema(
schema_path
)
partial_pseudo_rules = json.load("partial_pseudo_rules.json")
rules = [PseudoRule.from_json(rule) for rule in partial_pseudo_rules]
sc = SchemaTraverser(schema, rules)
# Matcher reglene for pseudonymisering med feltene i schema:
pseudo_rules = sc.match_rules()
matched_rules_output_path = "buckets/kilde/pseudo_rules.json" # Brukes for pseudonymisering i Kildomaten
# Lagrer fil med matchede regler for pseudonymisering
def write_rules(file_path: str, rules: list[PseudoRule]) -> None:
with gcsfs.GCSFileSystem().open(file_path, "w") as matched_rules_output:
matched_rules_output.write(
to_jsonable_python(rules)
)
write_rules(matched_rules_output_path, concrete_rules)Man kan da, med den ferdig utfylte pseudo_rules.json-filen, pseudonymisere dataene i Kildomaten ved å lese inn denne filen og sende den inn i add_rules()-funksjonen i dapla-toolbelt-pseudo som vist i eksempelet over.
Brukerveiledning
På grunn av den strenge tilgangsstyringen til dapla-pseudo-toolbelt og kildedata er det anbefalt å utvikle kode for overgangen fra kildedata til inndata i test-miljøet til teamet. I denne delen viser vi hvordan denne arbeidsflyten kan se ut, fra testing til en automatisert produksjon med ekte data, med et helt konkret eksempel.
Interaktiv utvikling
For å kunne kjøre pseudonymiseringen interaktivt i f.eks. en notebook i Jupyter, så må man jobbe i test-miljøet til teamet.
Kildomaten i test
Kommer snart.
Kildomaten i prod
Kommer snart.
Fotnoter
Et Dapla-team har både et test- og et prod-miljø. Kildomaten må være skrudd på i det miljøet du ønkser å benytte dapla-toolbelt-pseudo fra.↩︎
Med interaktiv jobbing menes at man skriver og kode og får tilbake output i samme verktøy. F.eks. er Jupyterlab et eksempel på et verktøy som lar deg jobbe interaktivt med data.↩︎
I Kildomaten må koden blant annet pakke inn i
main()-funksjon.↩︎Pandas og Polars dataframes er eksempler på dataformater som lever i minnet, og må konverteres før de skrives til et lagringsommråde. I praksis vil det ofte si at man jobber med dataframes når man jobber i verktøy som Jupyterlab, mens man skriver til lagringsområde når man er ferdig i Jupyterlab.↩︎
Standardalgoritmen i dapla-toolbelt-pseudo er den deterministiske krypteringsalgoritmen Deterministic Authenticated Encryption with Associated Data, eller DAEAD-algoritmen.↩︎
SNR-katalogen eies og tilbys av Team Register på Dapla.↩︎
Papis var et prosjekt med fokus på bakkesystemene i SSB som skulle bringe SSBs behandling av personopplysninger, som brukes i statistikkproduksjon, i samsvar med GDPR gjennom en enhetlig pseudonymiseringsløsning.↩︎
Generelt sett er det ikke å anbefale å benytte denne nøkkelen på annen informasjon enn fødselsnummer. Grunnen er at den er svakere enn andre algoritmer, der blant annet tekst som er kortere enn 4 karakter lang ikke blir pseudonymisert.↩︎
Datadoc er det nye systemet for dokumentasjon av datasett i SSB.↩︎
Redact er en algoritme som erstatter innholdet i et felt med karakteren
*. Dette er ikke en enkrypteringsalgoritme, og det er ikke mulig å de-pseudonymisere data som har bruktredact. Det kan likevel være nyttig i noen tilfeller hvis man ønsker å fjerne irrelevant data.↩︎
