-
Notifications
You must be signed in to change notification settings - Fork 18
/
storage.go
172 lines (140 loc) · 4.91 KB
/
storage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package authdb
import (
"bytes"
"context"
"encoding/hex"
"time"
"github.com/zeebo/errs"
)
// Invalid is the class of error that is returned for invalid records.
var Invalid = errs.Class("invalid")
// KeyHashError is a class of key hash errors.
var KeyHashError = errs.Class("key hash")
// Record holds encrypted credentials alongside metadata.
type Record struct {
SatelliteAddress string
PublicProjectID []byte
MacaroonHead []byte // 32 bytes probably
EncryptedSecretKey []byte
EncryptedAccessGrant []byte
ExpiresAt *time.Time
Public bool // if true, knowledge of secret key is not required
}
// FullRecord extends Record and includes invalidation information.
type FullRecord struct {
Record
CreatedAt time.Time
InvalidatedAt time.Time
InvalidationReason string
}
// IsInvalid returns whether the record was invalidated.
func (f FullRecord) IsInvalid() bool {
return f.InvalidationReason != "" || !f.InvalidatedAt.IsZero()
}
// EqualWithinDuration checks if this FullRecord is equal to another,
// comparing time.Time fields (CreatedAt, ExpiresAt, InvalidatedAt) using a given margin of error.
func (f FullRecord) EqualWithinDuration(other FullRecord, dur time.Duration) bool {
if f.SatelliteAddress != other.SatelliteAddress || f.InvalidationReason != other.InvalidationReason {
return false
}
if !bytes.Equal(f.MacaroonHead, other.MacaroonHead) ||
!bytes.Equal(f.EncryptedSecretKey, other.EncryptedSecretKey) ||
!bytes.Equal(f.EncryptedAccessGrant, other.EncryptedAccessGrant) {
return false
}
if f.ExpiresAt == nil {
if other.ExpiresAt != nil {
return false
}
} else if other.ExpiresAt == nil {
return false
}
if f.InvalidatedAt.IsZero() {
if !other.InvalidatedAt.IsZero() {
return false
}
} else if other.InvalidatedAt.IsZero() {
return false
}
if f.ExpiresAt != nil && other.ExpiresAt != nil && !withinDuration(*f.ExpiresAt, *other.ExpiresAt, dur) {
return false
}
if !withinDuration(f.CreatedAt, other.CreatedAt, dur) || !withinDuration(f.InvalidatedAt, other.InvalidatedAt, dur) {
return false
}
return f.Public == other.Public
}
func withinDuration(t1 time.Time, t2 time.Time, dur time.Duration) bool {
if t1.After(t2) {
t1, t2 = t2, t1
}
return t2.Sub(t1) < dur
}
// KeyHashSizeEncoded is the length of a hex encoded KeyHash.
const KeyHashSizeEncoded = 64
// KeyHash is the key under which Records are saved.
type KeyHash [32]byte
// SetBytes sets the key hash from bytes.
func (kh *KeyHash) SetBytes(v []byte) error {
if len(v) > len(KeyHash{}) {
return KeyHashError.New("v exceeds the acceptable length")
}
*kh = KeyHash{}
copy(kh[:], v)
return nil
}
// FromHex sets the key hash from a hex encoded string.
func (kh *KeyHash) FromHex(encoded string) error {
if len(encoded) != KeyHashSizeEncoded {
return KeyHashError.New("length expected to be %d, was %d", KeyHashSizeEncoded, len(encoded))
}
bytes, err := hex.DecodeString(encoded)
if err != nil {
return KeyHashError.New("error decoding key hash: %w", err)
}
if err := kh.SetBytes(bytes); err != nil {
return KeyHashError.New("error setting key hash bytes: %w", err)
}
return nil
}
// ToHex converts a key hash to a hex encoded string.
func (kh KeyHash) ToHex() string {
return hex.EncodeToString(kh.Bytes())
}
// Bytes returns the bytes for key hash.
func (kh KeyHash) Bytes() []byte { return kh[:] }
// Storage is meant to be the storage backend for Auth Service's database, with
// the ability to store and retrieve records saved under key hashes.
type Storage interface {
// Put stores the record.
// It is an error if the key already exists.
Put(ctx context.Context, keyHash KeyHash, record *Record) (err error)
// Get retrieves the record.
// It returns (nil, nil) if the key does not exist.
// If the record is invalid, the error contains why.
Get(ctx context.Context, keyHash KeyHash) (record *Record, err error)
// HealthCheck ensures the storage backend works and returns an error
// otherwise.
HealthCheck(ctx context.Context) error
// Run runs the storage backend.
Run(ctx context.Context) error
// Close closes the storage backend.
Close() error
}
// StorageAdmin extends Storage by allowing administrative queries to Auth
// Service's database.
type StorageAdmin interface {
Storage
// GetFullRecord retrieves a record with information relevant to auth service administration.
// It returns (nil, nil) if the key does not exist.
GetFullRecord(ctx context.Context, keyHash KeyHash) (record *FullRecord, err error)
// Invalidate invalidates the record.
Invalidate(ctx context.Context, keyHash KeyHash, reason string) error
// Unpublish unpublishes the record; this way it's not accessible through,
// e.g., Link Sharing Service.
Unpublish(ctx context.Context, keyHash KeyHash) error
// Delete deletes the record.
Delete(ctx context.Context, keyHash KeyHash) error
}