Documentation
¶
Overview ¶
Package data makes copies of a database that are safe to share with CI: every column of every table has a rule (classify.yaml), personal values are replaced deterministically under a project key, and the copy goes to another database and/or a byte-identical SQL dump. It is pseudonymisation, not anonymisation in the sense of the GDPR: whoever holds the key and the original data can link the two.
The package stands alone (no command-line or configuration code), so it can be published separately later. Entry points:
- ReadSchema: tables, columns, primary and foreign keys (Schema.Order, Schema.References for a row graph);
- Classify and MergeRules: proposals for columns without a rule;
- LoadRules, ParseRules, Rules.Check: the rules and their fit to a schema;
- Anonymize: database → database and/or dump, streaming;
- Subset: as Options.Filter, a sample with closed references (the subset: section of classify.yaml);
- PersonalValues: the original personal values, for a leak check;
- Verify: the two checks of an output (detector and leak check);
- NewReplacer: the strategies on single values.
MySQL, MariaDB and PostgreSQL are supported.
Index ¶
- Constants
- Variables
- func MergeRules(raw []byte, c *Classification) ([]byte, error)
- func PersonalValues(ctx context.Context, src Database, rules *Rules, det *pii.Detector, ...) error
- type CheckError
- type Classification
- type Column
- type ColumnProposal
- type ColumnReport
- type ColumnRule
- type Database
- type FilePlaceholder
- type FileValue
- type Filter
- type ForeignKey
- type Options
- type PersonalValue
- type Ref
- type Reference
- type Replacer
- func (r *Replacer) Digest(value string) [32]byte
- func (r *Replacer) Email(s string) (string, error)
- func (r *Replacer) ForColumn(c *Column, strategy string) replaceFn
- func (r *Replacer) Number(s string) (string, error)
- func (r *Replacer) Password(s string) (out string, known bool, err error)
- func (r *Replacer) Reserve(v PersonalValue) error
- func (r *Replacer) Token(s string) (string, error)
- func (r *Replacer) Universal(s string) (string, error)
- type Report
- type Rules
- type SampleReport
- type SampleRule
- type Schema
- type SchemaFinding
- type SchemaRule
- type Stmt
- type Subset
- type SubsetRules
- type Table
- type TableProposal
- type TableReport
- type TableRule
- type VerifyFinding
- type VerifyIndex
- type VerifyOptions
- type VerifyReport
Constants ¶
const ( KindKeep = "keep" // copied as it is KindPersonal = "personal" // replaced by a strategy KindSerializedPHP = "serialized-php" // PHP serialize() output: the table is emptied or the column nulled KindFile = "file" // a path to a file; the file gets a placeholder (FilePlaceholder) )
Column kinds.
const ( StrategyUniversal = "universal" StrategyEmail = "email" StrategyPassword = "password" StrategyToken = "token" StrategyFreeText = "free-text" StrategyHTML = "html" StrategyJSON = "json" StrategyNull = "null" StrategyEmpty = "empty" )
Strategies of personal columns.
const ( ActionCopy = "copy" // rows are copied, every column by its rule (the default) ActionEmpty = "empty" // the structure is copied without rows ActionSkip = "skip" // the table is left out entirely )
Table actions.
const ( SerializedEmptyTable = "empty-table" SerializedNull = "null" )
Serialized PHP actions.
const ( MySQL = "mysql" // MySQL and MariaDB Postgres = "postgres" )
Drivers.
const ( CatText = "text" // character data (and UUID): every strategy CatNumeric = "numeric" // integers and decimals: universal or token on the digits CatBinary = "binary" // bytes: keep, null or empty CatJSON = "json" // a JSON type: json, null or keep CatOther = "other" // dates, enums, network addresses, geometry…: keep or null )
Column categories decide which strategies fit a column.
const ( SampleAll = "all" // every row (small lookup tables) SampleNone = "none" // no rows )
Sample modes of a table, the one-word forms of a SampleRule.
const ( OrderLatest = "latest" // highest primary key first (the default) OrderFirst = "first" // lowest primary key first )
Orders of the rows a sample starts from.
const ( NullablePull = "pull" // the referenced row is taken in (the default) NullableNull = "null" // the reference is set to NULL )
What happens to a nullable reference whose row is not in the sample.
const ( CheckDetector = "detector" // the personal-data detector over the output CheckLeak = "leak" // an original personal value in the output )
Checks of data verify.
const DumpFormat = 1
DumpFormat is the version of the dump format; it changes when the same input would give different bytes.
const RulesVersion = 1
RulesVersion is the version of classify.yaml this build reads.
const TestPassword = "driftgate"
TestPassword is the password every replaced password hash verifies with.
Variables ¶
var ErrNotTarget = errors.New("not a DriftGate target")
ErrNotTarget means the target database holds objects but was not written by DriftGate (it has no marker), so it is never dropped, not even with Replace.
var ErrTargetExists = errors.New("target exists")
ErrTargetExists means the target database has tables and replacing it was not asked for.
var ErrUnsupported = errors.New("not supported yet")
ErrUnsupported means the database has objects the module cannot copy yet.
Functions ¶
func MergeRules ¶
func MergeRules(raw []byte, c *Classification) ([]byte, error)
MergeRules adds the proposals to classify.yaml and removes the rules for what no longer exists; every other rule, and its comments, stays as it is.
func PersonalValues ¶
func PersonalValues(ctx context.Context, src Database, rules *Rules, det *pii.Detector, fn func(PersonalValue) error) error
PersonalValues streams the original personal values of the source to fn, table by table and row by row in the same order as Anonymize: whole values of personal columns, the values under personal JSON paths, and what the detector finds in free text, HTML, JSON and serialized PHP. Rows of emptied and skipped tables never reach the output and are not read.
The values are originals: fn should hash them right away (Replacer.Digest) and never write them anywhere. Short values (an initial, a two-digit number) may appear in any output by chance; the caller decides which are long enough to count.
Types ¶
type CheckError ¶
type CheckError struct {
Unclassified []string // table.column without a rule (e.g. new after a migration)
Stale []string // rules for tables or columns that no longer exist
Proposed []string // rules data classify proposed and nobody confirmed yet
Invalid []string // rules that do not fit the column (type, NOT NULL, references)
Schema []string // schema objects with personal-looking literals nobody confirmed
}
CheckError lists everything that stops a run before it writes anything.
func (*CheckError) Error ¶
func (e *CheckError) Error() string
type Classification ¶
type Classification struct {
Tables []TableProposal
Stale []Ref
Unconfirmed []Ref
// Schema proposes confirming what the detector finds in schema objects;
// StaleSchema are confirmations for objects where it finds nothing now.
Schema []SchemaFinding
StaleSchema []string
}
Classification is what data classify found: rules to propose for tables and columns that have none, rules for what no longer exists, and proposals nobody confirmed yet. It never holds a sampled value.
func Classify ¶
func Classify(ctx context.Context, src Database, rules *Rules, det *pii.Detector, sample int) (*Classification, error)
Classify proposes rules for every table and column of the source that classify.yaml does not cover, from the names and types of the columns and from up to sample values of each (in primary-key order, so the proposal is the same every time). Existing rules are never changed.
func (*Classification) Changed ¶
func (c *Classification) Changed() bool
Changed reports whether the rules need a change.
type Column ¶
type Column struct {
Name string
Type string // as the server names it: varchar(255), jsonb, int(10) unsigned…
Category string
Nullable bool
Generated bool // computed by the server: never written
Unique bool // alone in a unique index or the primary key
// Collation decides which values are equal (MySQL: every text column;
// PostgreSQL: one other than the type's default). Replaced values that
// are equal under it get stand-ins that are equal under it.
Collation string
// contains filtered or unexported fields
}
Column is one column of a table.
type ColumnProposal ¶
type ColumnProposal struct {
Column string
Rule *ColumnRule
}
ColumnProposal is a proposed rule; it carries proposed: true and a reason with counts only.
type ColumnReport ¶
type ColumnReport struct {
Name string `json:"name"`
Rule string `json:"rule"` // keep, file, serialized-php or the strategy
Values int64 `json:"values"` // values that are not NULL
Parts int64 `json:"parts,omitempty"` // personal parts replaced in free text, HTML or JSON
Unknown int64 `json:"unknown_format,omitempty"` // password hashes of no known format, made unusable
}
ColumnReport counts the replacements in one column.
type ColumnRule ¶
type ColumnRule struct {
Kind string `yaml:"kind"`
Strategy string `yaml:"strategy,omitempty"`
Paths map[string]string `yaml:"paths,omitempty"` // json: JSON path glob → strategy (or keep)
Other string `yaml:"other,omitempty"` // json: strings under no path; default free-text
Action string `yaml:"action,omitempty"` // serialized-php: empty-table (default) or null
Options map[string]string `yaml:"options,omitempty"` // file: passed to the FilePlaceholder
Reason string `yaml:"reason,omitempty"`
Proposed bool `yaml:"proposed,omitempty"` // written by data classify; a person removes it
}
ColumnRule is the rule of one column. In YAML it is either a mapping or, for the plain cases, one word: keep, file, serialized-php or the name of a strategy (which implies kind personal).
func (*ColumnRule) MarshalYAML ¶
func (c *ColumnRule) MarshalYAML() (any, error)
func (*ColumnRule) UnmarshalYAML ¶
func (c *ColumnRule) UnmarshalYAML(n *yaml.Node) error
type Database ¶
type Database struct {
Driver string // mysql (MariaDB too) or postgres
// DSN reaches the server: mysql://user:pass@host:port/ or the Go driver's
// own form for MySQL, a postgres:// URL for PostgreSQL. Its database part
// is ignored (for PostgreSQL it is the database that creates and drops
// the target, default postgres).
DSN string
Name string // the database
// Dial, when set, makes every connection to the server: only to an
// allowed host (dbx.AllowDial). A staging database made from the
// target keeps it.
Dial dbx.DialFunc
}
Database names one database on a server.
type FilePlaceholder ¶
FilePlaceholder makes a placeholder for the file a file column names (same name, type and dimensions, neutral content) and returns the value the column gets in the output. Without one, a file rule stops the run before anything is written.
type FileValue ¶
type FileValue struct {
Table, Column string
Path string // the original value of the column
Options map[string]string // the rule's options from classify.yaml
}
FileValue is one value of a file column: the path the application stored.
type Filter ¶
type Filter interface {
// Where is an SQL condition on the rows of a table, ANDed to the read.
Where(t *Table) string
// Keep decides about one row after the read; values as text, nil for NULL.
// It may set values to nil (data subset nulls references that leave the
// sample) before the row is replaced.
Keep(t *Table, cols []*Column, row [][]byte) bool
}
Filter narrows the rows that are read (data subset). Both methods may keep everything: Where returns "" and Keep true.
type ForeignKey ¶
type ForeignKey struct {
Name string
Table string
Columns []string
RefTable string
RefColumns []string
}
ForeignKey is a reference from rows of Table to rows of RefTable.
type Options ¶
type Options struct {
Source Database // only read, in one read-only snapshot
// Target is written under a staging name and takes its own name when
// the run and Verify have passed; nil writes no database. It must be
// another database than the source (told apart by the servers'
// identities) and, when it holds objects, one DriftGate wrote.
Target *Database
// Replace allows dropping a target database DriftGate wrote that
// already holds objects.
Replace bool
Dump io.Writer // the deterministic SQL dump; nil writes none
// Verify checks the output before it is made: the target database under
// its staging name (nil without a target) and the dump, written and
// flushed. An error leaves the target as it was and is returned as is.
Verify func(ctx context.Context, staging *Database) error
// Index, when not nil, is filled with what data verify looks for, in
// the run's own pass over the original personal values of the source:
// Verify with VerifyOptions.Index then reads the source no more.
Index *VerifyIndex
Rules *Rules
Key []byte // the project key; the replacement key is derived from it
Files FilePlaceholder
PII *pii.Detector // finds personal parts of free text; nil: the default detector
Filter Filter
// BatchRows is the most rows per INSERT (default 500); a statement also
// ends after about 1 MiB.
BatchRows int
Log io.Writer // progress, one line per table; never values
}
Options configure Anonymize.
type PersonalValue ¶
type PersonalValue struct {
Table, Column string
// Kind is the strategy of a whole value (universal, email, token,
// password, null, empty), or for a part of free text, HTML, JSON or
// serialized PHP the detector's kind (email, phone, jmbg, card, iban) or
// the strategy of its JSON path.
Kind string
Value string
// Category is the category of the column (CatText, CatNumeric…); a
// number inside a JSON value is CatNumeric.
Category string
// contains filtered or unexported fields
}
PersonalValue is one original personal value of the source, for the leak check of data verify: none of them may appear anywhere in the output.
type Reference ¶
type Reference struct {
From string `yaml:"from"`
To string `yaml:"to,omitempty"` // omitted: From is a foreign key of the database
// When limits a declared reference to rows whose columns have these
// values (a polymorphic type column: commentable_type: App\Models\Post).
When map[string]string `yaml:"when,omitempty"`
Nullable string `yaml:"nullable,omitempty"` // pull or null; default subset.nullable
}
Reference is a reference between columns. From and To are written table(column, …).
type Replacer ¶
type Replacer struct {
// contains filtered or unexported fields
}
Replacer turns values into stand-ins deterministically: the same input under the same key always gives the same output, in every table and run, so joins, uniqueness and a CI baseline survive. Different inputs keep different outputs: a collision is resolved by the next candidate, and the first value (in processing order) to reach an output keeps it.
func NewReplacer ¶
NewReplacer derives the replacement key from the project key.
func (*Replacer) Digest ¶
Digest is a keyed hash of a value for the leak check: data verify looks for the digests of original personal values in the output without keeping the values themselves. It uses its own derived key.
func (*Replacer) Email ¶
Email replaces the local part like Universal and the domain label by label, with the top-level domain set to "invalid" (reserved, RFC 2606), so no stand-in can ever reach a real mailbox. The same domain always gets the same stand-in; addresses stay distinct.
func (*Replacer) ForColumn ¶
ForColumn returns the strategy for the values of one column: Universal and Email follow the column's collation, so values it counts as equal (case, accents, trailing spaces) get stand-ins it counts as equal, and values it counts as different get stand-ins it counts as different. A numeric column gets Number.
func (*Replacer) Number ¶
Number replaces the digits of a number and keeps the rest (sign, point, exponent); a stand-in never gets a new leading zero, which a numeric column would drop (two stand-ins could then become equal). NaN and infinities stay: they hold nothing personal, and a stand-in for them does not exist.
func (*Replacer) Password ¶
Password replaces a password hash with a hash of TestPassword in the same format, recognised by its prefix: bcrypt ($2y$, $2a$, $2b$, cost kept), Argon2 ($argon2id$, $argon2i$), Django PBKDF2 (iterations kept, a fixed salt) and unsalted MD5, SHA-1 and SHA-256 in hex. A value in another format becomes a token-like stand-in that matches no password; known reports which.
func (*Replacer) Reserve ¶
func (r *Replacer) Reserve(v PersonalValue) error
Reserve takes one original value of the source (PersonalValues gives them all, before anything is replaced): no stand-in of another value may be equal to it, or the output would hold a real value of someone else (a replaced phone number that is another person's number). A value that would be replaced by Universal or Number is reserved in its namespace and in that of every collation loaded; a token in its own. E-mail stand-ins are at .invalid domains and can never be an original.
func (*Replacer) Token ¶
Token replaces a secret with a value of the same length and character classes: a hexadecimal value (also a UUID) stays hexadecimal in the same case, other letters and digits stay letters and digits, the rest stays. Tokens are case-sensitive, so case is not folded.
func (*Replacer) Universal ¶
Universal is the strategy without dictionaries or languages: letters become letters of the same script and case, digits digits; punctuation, spacing and length stay. A value that is a plain number gets what Number gives it, so the same digits in a text column and a numeric column (a polymorphic key) get the same stand-in.
type Report ¶
type Report struct {
Driver string `json:"driver"`
Tables []TableReport `json:"tables"`
Rows int64 `json:"rows"`
Seconds float64 `json:"seconds"`
// LeftOut are views and other statements left out because they need a
// table that is skipped.
LeftOut []string `json:"left_out,omitempty"`
}
Report says what a run did, in counts only: it never holds a value.
func Anonymize ¶
Anonymize copies the source database into the target database and/or a dump, with every personal value replaced by its rule. The source is read in one read-only snapshot, table by table in name order and row by row in primary-key order, streaming: no table is held in memory, and no original value is written anywhere. The same source, key and rules give the same output, byte for byte.
Before anything is written the rules are checked against the schema; a column without a rule is a *CheckError. The target database is written under a staging name and replaces the target only after the run and opt.Verify have passed; on any error, also a cancelled context, the staging database is dropped and an existing target stays as it was.
type Rules ¶
type Rules struct {
Version int `yaml:"version"`
Tables map[string]*TableRule `yaml:"tables"`
// Subset chooses the rows of data subset; data anonymize copies every row.
Subset *SubsetRules `yaml:"subset,omitempty"`
// Schema confirms what the detector finds in schema objects (comments,
// defaults, views, triggers, functions, checks), which are copied as
// they are: by object, the kinds a person checked and found not personal
// (a shared mailbox in a default). Anything else found stops a run.
Schema map[string]*SchemaRule `yaml:"schema,omitempty"`
}
Rules is classify.yaml: a rule for every column of every table, written by data classify as a proposal and confirmed by a person.
func LoadRules ¶
LoadRules reads classify.yaml; a file that does not exist gives empty rules (every column then counts as unclassified).
func ParseRules ¶
ParseRules reads the rules from YAML; an unknown key is an error.
func (*Rules) Check ¶
Check matches the rules against a schema: every column of every copied table must have a rule that fits it, and no rule may name a table or column the schema does not have. files says whether file rules can be served.
func (*Rules) CheckSchema ¶
CheckSchema runs the detector over the schema objects that are copied as they are (table definitions with their comments and defaults, views, triggers, functions, constraints): what it finds must be confirmed under schema: in classify.yaml, or the run stops before it writes anything.
type SampleReport ¶
type SampleReport struct {
Total int64 `json:"total"` // rows in the source
Roots int64 `json:"roots"` // rows the sample started from
Followed int64 `json:"followed"` // rows that reference followed rows of a parent
Parents int64 `json:"parents"` // rows taken in only because kept rows reference them
// Nulled are references set to NULL: their row is outside the sample
// (nullable null) or in a table without rows.
Nulled int64 `json:"nulled,omitempty"`
// Dangling are references to rows that are not in the source either
// (declared references without a foreign key); copied as they are.
Dangling int64 `json:"dangling,omitempty"`
}
SampleReport counts the sample of one table; never a value.
type SampleRule ¶
type SampleRule struct {
Mode string `yaml:"-"` // all or none (the one-word form)
Rows int `yaml:"rows,omitempty"` // at most this many roots
Percent float64 `yaml:"percent,omitempty"` // roots: this share of the rows, chosen by a hash of the primary key
Where string `yaml:"where,omitempty"` // roots: rows matching this SQL condition
Order string `yaml:"order,omitempty"` // latest (default) or first, by primary key
// Follow lists parent tables: the rows of this table that reference
// their roots or followed rows are kept too (a user's orders).
Follow []string `yaml:"follow,omitempty"`
// PerParent is the most followed rows per parent row (by Order); 0 is all.
PerParent int `yaml:"per_parent,omitempty"`
}
SampleRule chooses the rows of one table. In YAML it is all, none, or a mapping. Rows, Percent and Where choose the rows the sample starts from (roots); Follow adds the rows that reference kept rows of other tables.
func (*SampleRule) MarshalYAML ¶
func (s *SampleRule) MarshalYAML() (any, error)
func (*SampleRule) UnmarshalYAML ¶
func (s *SampleRule) UnmarshalYAML(n *yaml.Node) error
type Schema ¶
Schema is what the module knows about a database: its tables in name order, their references, and the statements that recreate the rest of the schema around the rows.
func ReadSchema ¶
ReadSchema reads the schema of a database: tables, columns, keys and foreign keys (data subset builds its row graph from them).
func (*Schema) Order ¶
Order lists the tables parents first: a table comes after every table its foreign keys point at, except along a cycle (self-references and loops are broken in name order). data subset walks the rows in this order.
func (*Schema) References ¶
func (s *Schema) References(table string) []ForeignKey
References lists the foreign keys that point at a table, in a stable order.
type SchemaFinding ¶
SchemaFinding is what the detector finds in one schema object, by kind, with a reason in counts.
type SchemaRule ¶
type SchemaRule struct {
Kinds []string `yaml:"kinds"`
Reason string `yaml:"reason,omitempty"`
Proposed bool `yaml:"proposed,omitempty"`
}
SchemaRule confirms the detector's findings in one schema object. In YAML it is a list of kinds, or a mapping with kinds, reason and proposed.
func (*SchemaRule) MarshalYAML ¶
func (c *SchemaRule) MarshalYAML() (any, error)
func (*SchemaRule) UnmarshalYAML ¶
func (c *SchemaRule) UnmarshalYAML(n *yaml.Node) error
type Stmt ¶
Stmt is one SQL statement of the schema. Compound statements (MySQL triggers) need another delimiter in a dump.
type Subset ¶
type Subset struct {
// contains filtered or unexported fields
}
Subset is the Filter of data subset: before the copy it chooses a sample of the source with closed references, in the same read-only snapshot, and during the copy it keeps the rows of the sample and nulls references the rules allow to leave the sample. Set it as Options.Filter with Rules that have a subset: section; the zero value is ready.
The sample is the least set of rows that holds
- the roots: per table the rows chosen by rows, percent and where (all: every row);
- the followed rows: rows of a table with follow that reference a root or followed row of a parent it follows (per_parent at most per row);
- the parents: for every kept row the rows its references point at, transitively, except along nullable references with the rule null.
Only roots and followed rows are followed down: a row taken in as a parent does not bring its children, or one user would bring in every table. The sample depends only on the source and the rules, never on the order of the work, so the same input gives the same sample.
Memory: the identity (primary key) of every kept row, and the values of kept rows in referenced columns, are held in memory (roughly 100 bytes per kept row and referenced key; tables are never held). The source is only read, so nothing can be kept there.
type SubsetRules ¶
type SubsetRules struct {
// Nullable is the default for nullable references whose row is outside
// the sample: pull (default) or null.
Nullable string `yaml:"nullable,omitempty"`
// Tables chooses rows per table. A table not listed has only the rows
// other kept rows reference.
Tables map[string]*SampleRule `yaml:"tables,omitempty"`
// References are declared for an application without foreign keys
// (polymorphic columns too), or change the nullable rule of a foreign key.
References []*Reference `yaml:"references,omitempty"`
}
SubsetRules is the subset: section of classify.yaml: which rows data subset starts from, which rows it follows down, and the references the database does not declare. Every row the sample keeps takes in the rows its references point at, transitively, so the copy has no dangling reference.
func (*SubsetRules) UnmarshalYAML ¶
func (s *SubsetRules) UnmarshalYAML(n *yaml.Node) error
type Table ¶
type Table struct {
Name string // as in classify.yaml: the table, or schema.table outside PostgreSQL's public schema
Columns []*Column
PrimaryKey []string
ForeignKeys []ForeignKey
// contains filtered or unexported fields
}
Table is one table: its columns in their order, its keys and the statements that create it.
type TableProposal ¶
type TableProposal struct {
Table string
New bool // the table has no rule at all
Action string // proposed table action; "" copies
Reason string
Columns []ColumnProposal // in the order of the table
}
TableProposal holds the proposals for one table.
type TableReport ¶
type TableReport struct {
Name string `json:"name"`
Action string `json:"action"` // copy, empty, skip, or emptied (a serialized PHP column)
Rows int64 `json:"rows"`
Columns []ColumnReport `json:"columns,omitempty"`
Sample *SampleReport `json:"sample,omitempty"` // data subset
}
TableReport is one table of the report.
type TableRule ¶
type TableRule struct {
Action string `yaml:"action,omitempty"`
Reason string `yaml:"reason,omitempty"`
Proposed bool `yaml:"proposed,omitempty"`
Columns map[string]*ColumnRule `yaml:"columns,omitempty"`
}
TableRule is the rule of one table and its columns.
type VerifyFinding ¶
type VerifyFinding struct {
Check string `json:"check"` // detector or leak
Output string `json:"output"` // "database <name>" or "dump"
Table string `json:"table"` // "(schema)" for a statement of a dump that holds no rows
Column string `json:"column,omitempty"`
// Kind is the detector's kind (email, phone, jmbg, card, iban), or for
// the leak check the kind of the original value (its strategy, or the
// detector's kind for a part of free text), with "/word" for one word of
// a name.
Kind string `json:"kind"`
Origin string `json:"origin,omitempty"` // leak: the table.column the original value comes from
Values int64 `json:"values"` // output values with it
Rows []string `json:"rows,omitempty"` // up to five: the primary key when it is numeric, else #n in key order
}
VerifyFinding is one kind of finding in one column of an output.
type VerifyIndex ¶
type VerifyIndex struct {
// contains filtered or unexported fields
}
VerifyIndex is what data verify knows about the original personal values of a source, as keyed hashes; Anonymize fills it (Options.Index) in its own pass over them, for Verify on the output of the same run. The values come from the source, by the same rules as PersonalValues gives them, and not from what anonymize did with them: the leak check finds what the copy let through as it does after a read of its own.
type VerifyOptions ¶
type VerifyOptions struct {
Source Database // the original, read in one snapshot: its personal values are looked for
Target *Database // an output database to check; nil checks none
Dump io.Reader // an output dump to check; nil checks none
Rules *Rules
Key []byte // the project key the output was made with
PII *pii.Detector // nil: the default detector
// Index, filled by Anonymize (Options.Index) under the same key, holds
// the original values to look for: the source is not read again, and
// the originals are those of the snapshot the output was copied from.
// The detector is the one Anonymize used.
Index *VerifyIndex
Log io.Writer
}
VerifyOptions configure Verify.
type VerifyReport ¶
type VerifyReport struct {
Originals int64 `json:"originals"` // distinct original values the leak check looked for
Values int64 `json:"values"` // output values scanned
Seconds float64 `json:"seconds"`
Findings []VerifyFinding `json:"findings"`
}
VerifyReport says what Verify found, in counts and places only: it never holds a value.
func Verify ¶
func Verify(ctx context.Context, opt VerifyOptions) (*VerifyReport, error)
Verify checks an output of Anonymize (a database, a dump or both) against its source with two independent checks:
- the detector looks at every text and JSON value of the output (JSON strings decoded, HTML entities resolved); an e-mail address at a .invalid domain, a known test password hash and the stand-in of a personal value (computed again under the key) are not findings;
- the leak check looks for every original personal value of the source (PersonalValues) in every value of the output, as a whole value and inside text, regardless of case, script (Serbian Cyrillic and Latin) and diacritics.
The original values are never kept: the leak check holds two keyed 64-bit hashes of each (a fast hash under a random per-run seed for the scan, and the start of Replacer.Digest to confirm a match), about 65 bytes per distinct value. A match found by the project's pii.allow patterns is not a finding. The source is read once, in one snapshot, unless opt.Index holds the originals already.
func (*VerifyReport) Clean ¶
func (r *VerifyReport) Clean() bool
Clean reports whether neither check found anything.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package files makes placeholders for uploaded files, so that a copy of an application's storage carries none of the original content.
|
Package files makes placeholders for uploaded files, so that a copy of an application's storage carries none of the original content. |