Documentation
¶
Overview ¶
Package sensitive provides a set functions to handle sensitive fields in structs, including:
Mask partially redacts sensitive data while preserving the format of the data type (aka kind). It uses a set of predefined masks (e.g 'email' 'ipv4_addr') and allows to register additional masks.
Redact replaces sensitive field values with a redaction symbol ('*') by default. The behavior can be customized through optional parameters.
Scan is a lower-level function that gives access to sensitive struct metadata and a fields replacer. This can be used to implement more advanced features such as client-side encryption.
Check determines whether a struct contains any sensitive data fields.
Package sensitive leverages Go struct tags to identify and categorize struct sensitive field. It supports the following tag IDs `sensitive`, `pii`, `sens`. Here's an example:
type Profile struct {
Email string `sensitive:"data,kind=email"`
Fullname string `sensitive:"data,kind=name"`
Role string
}
Applying the default masking logic:
var profile := Profile{
Email: "eric.prosacco@example.com",
Fullname: "Eric Prosacco",
Role: "Teacher",
}
_ = sensitive.Mask(&profile)
// After masking:
//
// Profile{
// Email: "****.********@example.com",
// Fullname: "*************",
// Role: "Teacher",
// }
// Notes:
// - The default behavior of the `email` mask is to hide the local part while revealing the domain part.
// - The library does not provide a default mask for `name`; therefore, the default redaction behavior is applied.
// - You may consider defining a specific `name` mask and registering it using [mask.Register].
// - The Role field is not tagged as sensitive, so it remains unchanged.
Applying a custom redact logic:
type Profile struct {
Email string `sensitive:"data,kind=email"`
Fullname string `sensitive:"data,kind=name"`
Role string
}
var profile := Profile{
Email: "eric.prosacco@example.com",
Fullname: "Eric Prosacco",
Role: "Teacher",
}
option := func(rc *sensitive.RedactConfig) {
rc.RedactFunc = func(fr sensitive.FieldReplace, val string) (string, error) {
switch fr.Kind {
case "email":
return "ghost@unknown.net", nil
case "name":
return "Ghost", nil
}
return strings.Repeat("*", len(val)), nil
}
}
_ = sensitive.Redact(&profile, option)
// After redacting:
//
// Profile{
// Email: "ghost@unknown.net",
// Fullname: "Ghost",
// Role: "Teacher",
// }
Index ¶
- Variables
- func Check(v any) (found bool, err error)
- func Mask(structPtr any, opts ...func(*RedactConfig)) error
- func Redact(structPtr any, opts ...func(*RedactConfig)) error
- func RedactDefaultFunc(_ FieldReplace, val string) (string, error)
- func WithRegisteredMasks(rc *RedactConfig)
- type FieldReplace
- type Masked
- type MaskedCopyConfig
- type RedactConfig
- type ReplaceFunc
- type Struct
- type TagOptions
- type TagPayload
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrInvalidTagConfiguration = errors.New("invalid 'sensitive' tag configuration") ErrUnsupportedType = errors.New("unsupported 'sensitive' type") ErrUnsupportedFieldType = errors.New("'sensitive' field type must be convertible to string") ErrMultipleNestedSubjectID = errors.New("potential multiple nested subject IDs") ErrSubjectIDNotFound = errors.New("subject ID is not found") )
var (
ErrFailedToMaskCopy = errors.New("failed to mask copy")
)
var (
ErrRedactFuncNotFound = errors.New("redact function not found")
)
Functions ¶
func Check ¶
Check verifies whether the provided struct contains sensitive data fields. It returns an error if the 'sensitive' tag is misconfigured or if the value parameter is not a struct or a pointer to a struct.
func Mask ¶
func Mask(structPtr any, opts ...func(*RedactConfig)) error
Mask partially redacts sensitive data based on their type (aka kind).
It is simply a facade function that calls Redact with WithRegisteredMasks option.
Use mask.Register to register additional masks.
Example ¶
Example of masking with a custom registered mask (ex: Belgian National Register Number)
package main
import (
"errors"
"log"
"regexp"
sensitive "github.com/ln80/struct-sensitive"
"github.com/ln80/struct-sensitive/internal/option"
"github.com/ln80/struct-sensitive/mask"
)
func main() {
type Profile struct {
Email string `sensitive:"data,kind=email"`
NRN string `sensitive:"data,kind=be_nrn"`
Fullname string `sensitive:"data"`
}
// Define the Belgian National Register Number (be_nrn) mask behavior.
//
// Assuming, based on business requirements, revealing the birth date is acceptable by default.
//
// You can evolve the mask behavior by adding options to the struct.
// However, note that only the default options are used by the [sensitive.Mask] function.
// Alternatively, you can define and apply the mask directly in your code if you need custom behavior for specific cases.
type BeNRNConfig struct {
RevealBirthDate bool
}
BeNRNRegex := regexp.MustCompile(`^(\d{2})\.(\d{2})\.(\d{2})-(\d{3})-(\d{2})$`)
BeNRNMask := func(val string, opts ...func(*mask.Config[BeNRNConfig])) (masked string, err error) {
cfg := mask.DefaultConfig(BeNRNConfig{
RevealBirthDate: true,
})
option.Apply(&cfg, opts)
matches := BeNRNRegex.FindStringSubmatch(val)
if matches == nil {
return "", errors.New("invalid BE_NRN format")
}
if !cfg.Kind.RevealBirthDate {
masked = "**.**.**-***-**"
return
}
masked = matches[1] + "." + matches[2] + "." + matches[3] + "-***-**"
return
}
mask.Register("be_nrn", mask.DefaultMasker(BeNRNMask))
p := Profile{
Email: "eric.prosacco@example.com",
NRN: "85.12.25-123-45",
Fullname: "Eric Prosacco",
}
err := sensitive.Mask(&p)
if err != nil {
log.Fatal(err)
}
print(p)
}
Output: Profile{ Email: "*************@example.com", NRN: "85.12.25-***-**", Fullname: "*************", }
func Redact ¶
func Redact(structPtr any, opts ...func(*RedactConfig)) error
Redact redacts sensitive data from struct field values by replacing each character with '*'.
It returns an error if the value is not a struct pointer, the 'sensitive' tag is misconfigured, or if the redact function is nil.
Optionally, you can override the default redact function by passing a custom one.
Example ¶
Example of a basic usage
package main
import (
"log"
sensitive "github.com/ln80/struct-sensitive"
)
func main() {
type Profile struct {
Email string `sensitive:"data"`
Fullname string `sensitive:"data"`
Role string
}
p := Profile{
Email: "eric.prosacco@example.com",
Fullname: "Eric Prosacco",
Role: "Teacher",
}
err := sensitive.Redact(&p)
if err != nil {
log.Fatal(err)
}
print(p)
}
Output: Profile{ Email: "*************************", Fullname: "*************", Role: "Teacher", }
Example (Second) ¶
Example of a custom Redact function
package main
import (
"log"
"strings"
sensitive "github.com/ln80/struct-sensitive"
)
func main() {
type Profile struct {
Email string `sensitive:"data,kind=email"`
Fullname string `sensitive:"data,kind=name"`
Role string
}
p := Profile{
Email: "eric.prosacco@example.com",
Fullname: "Eric Prosacco",
Role: "Teacher",
}
err := sensitive.Redact(&p, func(rc *sensitive.RedactConfig) {
rc.RedactFunc = func(fr sensitive.FieldReplace, val string) (string, error) {
switch fr.Kind {
case "email":
return "ghost@unknown.net", nil
case "name":
return "Ghost", nil
}
return strings.Repeat("*", len(val)), nil
}
})
if err != nil {
log.Fatal(err)
}
print(p)
}
Output: Profile{ Email: "ghost@unknown.net", Fullname: "Ghost", Role: "Teacher", }
func RedactDefaultFunc ¶
func RedactDefaultFunc(_ FieldReplace, val string) (string, error)
func WithRegisteredMasks ¶
func WithRegisteredMasks(rc *RedactConfig)
WithRegisteredMasks returns an option that force redaction using the registered masks, including the predefined one e.g. `email`, `ipv4_addr`, `credit_card`.
Use mask.Register to override or register new masks.
Types ¶
type FieldReplace ¶
type FieldReplace struct {
// SubjectID is the identifier for the sensitive data subject, resolved at the struct level.
// This field may be empty if it is not required by the calling function.
SubjectID string
// Name is the name of the sensitive field.
Name string
// RType is the original type of the sensitive field.
// Note that this type must be convertible to a string.
RType reflect.Type
// Kind is the user-defined type of sensitive data, defined as an option in the 'sensitive' tag.
Kind string
// Options are the options specified in the 'sensitive' tag.
Options TagOptions
}
FieldReplace contains metadata for sensitive fields.
type Masked ¶ added in v0.6.0
type Masked[T any] struct { // contains filtered or unexported fields }
Masked is a wrapper that contains both the original value and masked copy
func MaskedCopy ¶ added in v0.6.0
func MaskedCopy[T any](v T, opts ...func(*MaskedCopyConfig)) *Masked[T]
MaskedCopy returns a masked copy of the given value. It panics in case of failure.
func NewMaskedCopy ¶ added in v0.6.0
func NewMaskedCopy[T any](v T, opts ...func(*MaskedCopyConfig)) (*Masked[T], error)
NewMaskedCopy returns a new masked copy of the given value. It fails if it can't copy the value or the mask config is invalid.
func (Masked[T]) MarshalJSON ¶ added in v0.6.0
MarshalJSON implements json.Marshaler
func (Masked[T]) Reveal ¶ added in v0.6.0
func (r Masked[T]) Reveal() T
Reveal reveals the original value without applying masks
type MaskedCopyConfig ¶ added in v0.6.0
type MaskedCopyConfig struct {
DeepCopy bool // default false
}
type RedactConfig ¶
type RedactConfig struct {
// RequireSubjectID force the subjectID resolution from the struct value.
// This config is disabled by default.
RequireSubjectID bool
// RedactFunc overrides the default redaction function `RedactDefaultFunc`.
RedactFunc ReplaceFunc
}
RedactConfig presents the configuration required by `sensitive.Redact`.
type ReplaceFunc ¶
type ReplaceFunc func(fr FieldReplace, val string) (string, error)
ReplaceFunc is a callback function executed by the [Struct.Replace] method. It receives the original value of the sensitive field, converted to a string, and returns the new value as a string along with any error that may occur.
type Struct ¶
type Struct interface {
// Replace accepts a replacement function and applies it to each sensitive data field.
Replace(fn ReplaceFunc) error
// SubjectID returns the resolved SubjectID of the sensitive struct.
// It panics if the SubjectID is not resolved.
SubjectID() string
// HasSensitive indicates whether the struct contains sensitive data fields.
HasSensitive() bool
// contains filtered or unexported methods
}
Struct provides an accessor for sensitive struct fields and subject identifiers.
func Scan ¶
Scan inspects the given value and returns an accessor for the sensitive struct. It returns an error if the value is not a pointer to a struct or if the 'sensitive' tag is misconfigured.
The Struct accessor and the Scan function are low-level components. In most cases, you should consider using the Redact or Mask functions instead.
type TagOptions ¶
TagOptions presents a map of options configured at the `sensitive` tag.
func (TagOptions) Get ¶
func (m TagOptions) Get(name string) string
type TagPayload ¶
type TagPayload struct {
// ID is the identifier of the tag, e.g., `sensitive`, `pii`.
ID string
// Name is the name of the sensitive tag, e.g., `data`, `subjectID`.
Name string
// Options represents the options associated with the sensitive tag.
Options TagOptions
}
TagPayload represents the metadata for a sensitive tag.
func FieldTag ¶
func FieldTag(v any, field string) (*TagPayload, error)
FieldTag extracts and parses the `sensitive` tag of the specified field in the given struct.
It returns an error if the value is neither a struct nor a pointer to a struct, or if the field is not found. It returns an empty value if the `sensitive` tag is misconfigured.
func MustFieldTag ¶
func MustFieldTag(v any, field string) TagPayload
MustFieldTag extracts and parses the `sensitive` tag of the specified field in the given struct.
It panics if the value is neither a struct nor a pointer to a struct, if the field is not found, or if the `sensitive` tag is misconfigured.
Note: This function was primarily added to facilitate testing in downstream libraries.
func ParseTag ¶
func ParseTag(rt reflect.StructTag) *TagPayload
ParseTag searches for a sensitive tag in the given field's raw tag, parses it, and returns a representational payload. It returns nil if the tag is not found or is misconfigured.
func (TagPayload) Marshal ¶
func (p TagPayload) Marshal() string
Marshal returns back the string representation of the parsed tag.
func (TagPayload) String ¶ added in v0.6.0
func (p TagPayload) String() string