mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-02 11:56:02 +02:00
core/zero: add pseudonymization key (#5290)
This commit is contained in:
parent
982275b1bb
commit
ddbf2249a3
9 changed files with 44 additions and 20 deletions
|
@ -54,6 +54,8 @@ type Config struct {
|
||||||
ZeroClusterID string
|
ZeroClusterID string
|
||||||
// ZeroOrganizationID is the zero organization id, only set when in zero mode.
|
// ZeroOrganizationID is the zero organization id, only set when in zero mode.
|
||||||
ZeroOrganizationID string
|
ZeroOrganizationID string
|
||||||
|
// ZeroPseudonymizationKey is the zero key used to pseudonymize data, only set in zero mode.
|
||||||
|
ZeroPseudonymizationKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone creates a clone of the config.
|
// Clone creates a clone of the config.
|
||||||
|
|
|
@ -99,4 +99,5 @@ func applyBootstrapConfig(dst *config.Config, src *cluster_api.BootstrapConfig)
|
||||||
}
|
}
|
||||||
dst.ZeroClusterID = src.ClusterId
|
dst.ZeroClusterID = src.ClusterId
|
||||||
dst.ZeroOrganizationID = src.OrganizationId
|
dst.ZeroOrganizationID = src.OrganizationId
|
||||||
|
dst.ZeroPseudonymizationKey = src.PseudonymizationKey
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -63,6 +64,7 @@ func TestSecretWriter(t *testing.T) {
|
||||||
txt := "test"
|
txt := "test"
|
||||||
src := cluster_api.BootstrapConfig{
|
src := cluster_api.BootstrapConfig{
|
||||||
DatabrokerStorageConnection: &txt,
|
DatabrokerStorageConnection: &txt,
|
||||||
|
PseudonymizationKey: []byte{1, 2, 3},
|
||||||
}
|
}
|
||||||
|
|
||||||
writer = writer.WithOptions(writers.ConfigWriterOptions{
|
writer = writer.WithOptions(writers.ConfigWriterOptions{
|
||||||
|
@ -95,7 +97,13 @@ func TestSecretWriter(t *testing.T) {
|
||||||
"namespace": "pomerium",
|
"namespace": "pomerium",
|
||||||
},
|
},
|
||||||
"data": map[string]any{
|
"data": map[string]any{
|
||||||
"bootstrap.dat": `{"clusterId":"","databrokerStorageConnection":"test","organizationId":"","sharedSecret":null}`,
|
"bootstrap.dat": mustJSON(map[string]any{
|
||||||
|
"clusterId": "",
|
||||||
|
"databrokerStorageConnection": "test",
|
||||||
|
"organizationId": "",
|
||||||
|
"pseudonymizationKey": "AQID",
|
||||||
|
"sharedSecret": nil,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}, unstructured)
|
}, unstructured)
|
||||||
})
|
})
|
||||||
|
@ -152,3 +160,11 @@ func TestSecretWriter(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mustJSON(v any) string {
|
||||||
|
bs, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(bs)
|
||||||
|
}
|
||||||
|
|
|
@ -211,7 +211,7 @@ func (c *controller) runHealthChecksLeased(ctx context.Context, client databroke
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) runUsageReporter(ctx context.Context, client databroker.DataBrokerServiceClient) error {
|
func (c *controller) runUsageReporter(ctx context.Context, client databroker.DataBrokerServiceClient) error {
|
||||||
ur := usagereporter.New(c.api, c.bootstrapConfig.GetConfig().ZeroOrganizationID, time.Minute)
|
ur := usagereporter.New(c.api, c.bootstrapConfig.GetConfig().ZeroPseudonymizationKey, time.Minute)
|
||||||
return retry.WithBackoff(ctx, "zero-usage-reporter", func(ctx context.Context) error {
|
return retry.WithBackoff(ctx, "zero-usage-reporter", func(ctx context.Context) error {
|
||||||
// start the usage reporter
|
// start the usage reporter
|
||||||
return ur.Run(ctx, client)
|
return ur.Run(ctx, client)
|
||||||
|
|
|
@ -39,9 +39,9 @@ type usageReporterRecord struct {
|
||||||
|
|
||||||
// A UsageReporter reports usage to the zero api.
|
// A UsageReporter reports usage to the zero api.
|
||||||
type UsageReporter struct {
|
type UsageReporter struct {
|
||||||
api API
|
api API
|
||||||
organizationID string
|
pseudonymizationKey []byte
|
||||||
reportInterval time.Duration
|
reportInterval time.Duration
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
byUserID map[string]usageReporterRecord
|
byUserID map[string]usageReporterRecord
|
||||||
|
@ -49,11 +49,11 @@ type UsageReporter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new UsageReporter.
|
// New creates a new UsageReporter.
|
||||||
func New(api API, organizationID string, reportInterval time.Duration) *UsageReporter {
|
func New(api API, pseudonymizationKey []byte, reportInterval time.Duration) *UsageReporter {
|
||||||
return &UsageReporter{
|
return &UsageReporter{
|
||||||
api: api,
|
api: api,
|
||||||
organizationID: organizationID,
|
pseudonymizationKey: pseudonymizationKey,
|
||||||
reportInterval: reportInterval,
|
reportInterval: reportInterval,
|
||||||
|
|
||||||
byUserID: make(map[string]usageReporterRecord),
|
byUserID: make(map[string]usageReporterRecord),
|
||||||
updates: set.New[string](0),
|
updates: set.New[string](0),
|
||||||
|
@ -62,7 +62,7 @@ func New(api API, organizationID string, reportInterval time.Duration) *UsageRep
|
||||||
|
|
||||||
// Run runs the usage reporter.
|
// Run runs the usage reporter.
|
||||||
func (ur *UsageReporter) Run(ctx context.Context, client databroker.DataBrokerServiceClient) error {
|
func (ur *UsageReporter) Run(ctx context.Context, client databroker.DataBrokerServiceClient) error {
|
||||||
ctx = log.Ctx(ctx).With().Str("organization-id", ur.organizationID).Logger().WithContext(ctx)
|
ctx = log.Ctx(ctx).With().Logger().WithContext(ctx)
|
||||||
|
|
||||||
// first initialize the user collection
|
// first initialize the user collection
|
||||||
serverVersion, latestSessionRecordVersion, latestUserRecordVersion, err := ur.runInit(ctx, client)
|
serverVersion, latestSessionRecordVersion, latestUserRecordVersion, err := ur.runInit(ctx, client)
|
||||||
|
@ -76,7 +76,7 @@ func (ur *UsageReporter) Run(ctx context.Context, client databroker.DataBrokerSe
|
||||||
|
|
||||||
func (ur *UsageReporter) report(ctx context.Context, records []usageReporterRecord) error {
|
func (ur *UsageReporter) report(ctx context.Context, records []usageReporterRecord) error {
|
||||||
req := cluster.ReportUsageRequest{
|
req := cluster.ReportUsageRequest{
|
||||||
Users: convertUsageReporterRecords(ur.organizationID, records),
|
Users: convertUsageReporterRecords(ur.pseudonymizationKey, records),
|
||||||
}
|
}
|
||||||
return backoff.Retry(func() error {
|
return backoff.Retry(func() error {
|
||||||
log.Debug(ctx).Int("updated-users", len(req.Users)).Msg("reporting usage")
|
log.Debug(ctx).Int("updated-users", len(req.Users)).Msg("reporting usage")
|
||||||
|
@ -193,15 +193,15 @@ func (ur *UsageReporter) onUpdateUser(u *user.User) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertUsageReporterRecords(organizationID string, records []usageReporterRecord) []cluster.ReportUsageUser {
|
func convertUsageReporterRecords(pseudonymizationKey []byte, records []usageReporterRecord) []cluster.ReportUsageUser {
|
||||||
var users []cluster.ReportUsageUser
|
var users []cluster.ReportUsageUser
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
u := cluster.ReportUsageUser{
|
u := cluster.ReportUsageUser{
|
||||||
LastSignedInAt: record.lastSignedInAt,
|
LastSignedInAt: record.lastSignedInAt,
|
||||||
PseudonymousId: cryptutil.Pseudonymize(organizationID, record.userID),
|
PseudonymousId: cryptutil.Pseudonymize(pseudonymizationKey, record.userID),
|
||||||
}
|
}
|
||||||
if record.userEmail != "" {
|
if record.userEmail != "" {
|
||||||
u.PseudonymousEmail = cryptutil.Pseudonymize(organizationID, record.userEmail)
|
u.PseudonymousEmail = cryptutil.Pseudonymize(pseudonymizationKey, record.userEmail)
|
||||||
}
|
}
|
||||||
users = append(users, u)
|
users = append(users, u)
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ func TestUsageReporter(t *testing.T) {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}, "bQjwPpxcwJRbvsSMFgbZFkXmxFJ", time.Millisecond*100)
|
}, []byte("bQjwPpxcwJRbvsSMFgbZFkXmxFJ"), time.Millisecond*100)
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
|
@ -125,12 +125,12 @@ func Test_convertUsageReporterRecords(t *testing.T) {
|
||||||
|
|
||||||
tm1 := time.Date(2024, time.September, 11, 11, 56, 0, 0, time.UTC)
|
tm1 := time.Date(2024, time.September, 11, 11, 56, 0, 0, time.UTC)
|
||||||
|
|
||||||
assert.Empty(t, convertUsageReporterRecords("XXX", nil))
|
assert.Empty(t, convertUsageReporterRecords([]byte("XXX"), nil))
|
||||||
assert.Equal(t, []cluster.ReportUsageUser{{
|
assert.Equal(t, []cluster.ReportUsageUser{{
|
||||||
LastSignedInAt: tm1,
|
LastSignedInAt: tm1,
|
||||||
PseudonymousId: "T9V1yL/UueF/LVuF6XjoSNde0INElXG10zKepmyPke8=",
|
PseudonymousId: "T9V1yL/UueF/LVuF6XjoSNde0INElXG10zKepmyPke8=",
|
||||||
PseudonymousEmail: "8w5rtnZyv0EGkpHmTlkmupgb1jCzn/IxGCfvpdGGnvI=",
|
PseudonymousEmail: "8w5rtnZyv0EGkpHmTlkmupgb1jCzn/IxGCfvpdGGnvI=",
|
||||||
}}, convertUsageReporterRecords("XXX", []usageReporterRecord{{
|
}}, convertUsageReporterRecords([]byte("XXX"), []usageReporterRecord{{
|
||||||
userID: "ID",
|
userID: "ID",
|
||||||
userEmail: "EMAIL@example.com",
|
userEmail: "EMAIL@example.com",
|
||||||
lastSignedInAt: tm1,
|
lastSignedInAt: tm1,
|
||||||
|
@ -138,7 +138,7 @@ func Test_convertUsageReporterRecords(t *testing.T) {
|
||||||
assert.Equal(t, []cluster.ReportUsageUser{{
|
assert.Equal(t, []cluster.ReportUsageUser{{
|
||||||
LastSignedInAt: tm1,
|
LastSignedInAt: tm1,
|
||||||
PseudonymousId: "T9V1yL/UueF/LVuF6XjoSNde0INElXG10zKepmyPke8=",
|
PseudonymousId: "T9V1yL/UueF/LVuF6XjoSNde0INElXG10zKepmyPke8=",
|
||||||
}}, convertUsageReporterRecords("XXX", []usageReporterRecord{{
|
}}, convertUsageReporterRecords([]byte("XXX"), []usageReporterRecord{{
|
||||||
userID: "ID",
|
userID: "ID",
|
||||||
lastSignedInAt: tm1,
|
lastSignedInAt: tm1,
|
||||||
}}), "should leave empty email")
|
}}), "should leave empty email")
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pseudonymize pseudonymizes data by computing the HMAC-SHA256 of the data.
|
// Pseudonymize pseudonymizes data by computing the HMAC-SHA256 of the data.
|
||||||
func Pseudonymize(organizationID string, data string) string {
|
func Pseudonymize(key []byte, data string) string {
|
||||||
h := hmac.New(sha256.New, []byte(organizationID))
|
h := hmac.New(sha256.New, key)
|
||||||
_, _ = io.WriteString(h, data)
|
_, _ = io.WriteString(h, data)
|
||||||
bs := h.Sum(nil)
|
bs := h.Sum(nil)
|
||||||
return base64.StdEncoding.EncodeToString(bs)
|
return base64.StdEncoding.EncodeToString(bs)
|
||||||
|
|
|
@ -27,6 +27,7 @@ type BootstrapConfig struct {
|
||||||
// DatabrokerStorageConnection databroker storage connection string
|
// DatabrokerStorageConnection databroker storage connection string
|
||||||
DatabrokerStorageConnection *string `json:"databrokerStorageConnection,omitempty"`
|
DatabrokerStorageConnection *string `json:"databrokerStorageConnection,omitempty"`
|
||||||
OrganizationId string `json:"organizationId"`
|
OrganizationId string `json:"organizationId"`
|
||||||
|
PseudonymizationKey []byte `json:"pseudonymizationKey"`
|
||||||
|
|
||||||
// SharedSecret shared secret
|
// SharedSecret shared secret
|
||||||
SharedSecret []byte `json:"sharedSecret"`
|
SharedSecret []byte `json:"sharedSecret"`
|
||||||
|
|
|
@ -197,6 +197,9 @@ components:
|
||||||
description: databroker storage connection string
|
description: databroker storage connection string
|
||||||
organizationId:
|
organizationId:
|
||||||
type: string
|
type: string
|
||||||
|
pseudonymizationKey:
|
||||||
|
type: string
|
||||||
|
format: byte
|
||||||
sharedSecret:
|
sharedSecret:
|
||||||
type: string
|
type: string
|
||||||
format: byte
|
format: byte
|
||||||
|
@ -204,6 +207,7 @@ components:
|
||||||
required:
|
required:
|
||||||
- clusterId
|
- clusterId
|
||||||
- organizationId
|
- organizationId
|
||||||
|
- pseudonymizationKey
|
||||||
- sharedSecret
|
- sharedSecret
|
||||||
|
|
||||||
Bundle:
|
Bundle:
|
||||||
|
|
Loading…
Add table
Reference in a new issue