Help us shape the future of Go! Take the 2026 Go Developer Survey

data

package
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: Apache-2.0 Imports: 46 Imported by: 0

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

View Source
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.

View Source
const (
	StrategyUniversal = "universal"
	StrategyEmail     = "email"
	StrategyPassword  = "password"
	StrategyToken     = "token"
	StrategyFreeText  = "free-text"
	StrategyHTML      = "html"
	StrategyJSON      = "json"
	StrategyNull      = "null"
	StrategyEmpty     = "empty"
)

Strategies of personal columns.

View Source
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.

View Source
const (
	SerializedEmptyTable = "empty-table"
	SerializedNull       = "null"
)

Serialized PHP actions.

View Source
const (
	MySQL    = "mysql" // MySQL and MariaDB
	Postgres = "postgres"
)

Drivers.

View Source
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.

View Source
const (
	SampleAll  = "all"  // every row (small lookup tables)
	SampleNone = "none" // no rows
)

Sample modes of a table, the one-word forms of a SampleRule.

View Source
const (
	OrderLatest = "latest" // highest primary key first (the default)
	OrderFirst  = "first"  // lowest primary key first
)

Orders of the rows a sample starts from.

View Source
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.

View Source
const (
	CheckDetector = "detector" // the personal-data detector over the output
	CheckLeak     = "leak"     // an original personal value in the output
)

Checks of data verify.

View Source
const DumpFormat = 1

DumpFormat is the version of the dump format; it changes when the same input would give different bytes.

View Source
const RulesVersion = 1

RulesVersion is the version of classify.yaml this build reads.

View Source
const TestPassword = "driftgate"

TestPassword is the password every replaced password hash verifies with.

Variables

View Source
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.

View Source
var ErrTargetExists = errors.New("target exists")

ErrTargetExists means the target database has tables and replacing it was not asked for.

View Source
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.

func (Database) Hosts

func (d Database) Hosts() ([]string, error)

Hosts lists every host the driver may connect to with the DSN, as the driver itself reads it (PostgreSQL: several hosts, fallbacks, host= parameters, PG* variables); "" stands for a local socket.

type FilePlaceholder

type FilePlaceholder interface {
	Placeholder(ctx context.Context, f FileValue) (string, error)
}

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 Ref

type Ref struct{ Table, Column string }

Ref names a table, or a column of it.

func (Ref) String

func (r Ref) String() string

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, …).

func (*Reference) UnmarshalYAML

func (r *Reference) UnmarshalYAML(n *yaml.Node) error

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

func NewReplacer(projectKey []byte) (*Replacer, error)

NewReplacer derives the replacement key from the project key.

func (*Replacer) Digest

func (r *Replacer) Digest(value string) [32]byte

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

func (r *Replacer) Email(s string) (string, error)

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

func (r *Replacer) ForColumn(c *Column, strategy string) replaceFn

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

func (r *Replacer) Number(s string) (string, error)

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

func (r *Replacer) Password(s string) (out string, known bool, err error)

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

func (r *Replacer) Token(s string) (string, error)

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

func (r *Replacer) Universal(s string) (string, error)

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

func Anonymize(ctx context.Context, opt Options) (rep *Report, err error)

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

func LoadRules(path string) (*Rules, error)

LoadRules reads classify.yaml; a file that does not exist gives empty rules (every column then counts as unclassified).

func ParseRules

func ParseRules(raw []byte) (*Rules, error)

ParseRules reads the rules from YAML; an unknown key is an error.

func (*Rules) Check

func (r *Rules) Check(s *Schema, files bool) error

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

func (r *Rules) CheckSchema(s *Schema, det *pii.Detector) error

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.

func (*Rules) Marshal

func (r *Rules) Marshal() ([]byte, error)

Marshal writes the rules as YAML, tables and columns sorted.

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

type Schema struct {
	Driver string
	Tables []*Table
	// contains filtered or unexported fields
}

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

func ReadSchema(ctx context.Context, d Database) (*Schema, error)

ReadSchema reads the schema of a database: tables, columns, keys and foreign keys (data subset builds its row graph from them).

func (*Schema) Order

func (s *Schema) Order() []*Table

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.

func (*Schema) Table

func (s *Schema) Table(name string) *Table

Table finds a table by name.

type SchemaFinding

type SchemaFinding struct {
	Object string
	Kinds  []string
	Reason string
}

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

type Stmt struct {
	SQL      string
	Compound bool
	// contains filtered or unexported fields
}

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.

func (*Subset) Keep

func (s *Subset) Keep(t *Table, cols []*Column, row [][]byte) bool

Keep keeps the rows of the sample; on a kept row it nulls the references whose row stays outside the sample (nullable null, or a table without rows).

func (*Subset) Where

func (s *Subset) Where(t *Table) string

Where asks the source only for the kept rows when there are few of them.

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.

func (*Table) Column

func (t *Table) Column(name string) *Column

Column finds a column by name.

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.

func (*TableRule) UnmarshalYAML

func (t *TableRule) UnmarshalYAML(n *yaml.Node) error

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.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL