forked from juanfont/headscale
-
Notifications
You must be signed in to change notification settings - Fork 0
/
preauth_keys.go
128 lines (106 loc) · 3.2 KB
/
preauth_keys.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
package headscale
import (
"crypto/rand"
"encoding/hex"
"errors"
"time"
"gorm.io/gorm"
)
const errorAuthKeyNotFound = Error("AuthKey not found")
const errorAuthKeyExpired = Error("AuthKey expired")
const errorAuthKeyNotReusableAlreadyUsed = Error("AuthKey not reusable already used")
// PreAuthKey describes a pre-authorization key usable in a particular namespace
type PreAuthKey struct {
ID uint64 `gorm:"primary_key"`
Key string
NamespaceID uint
Namespace Namespace
Reusable bool
Ephemeral bool `gorm:"default:false"`
CreatedAt *time.Time
Expiration *time.Time
}
// CreatePreAuthKey creates a new PreAuthKey in a namespace, and returns it
func (h *Headscale) CreatePreAuthKey(namespaceName string, reusable bool, ephemeral bool, expiration *time.Time) (*PreAuthKey, error) {
n, err := h.GetNamespace(namespaceName)
if err != nil {
return nil, err
}
now := time.Now().UTC()
kstr, err := h.generateKey()
if err != nil {
return nil, err
}
k := PreAuthKey{
Key: kstr,
NamespaceID: n.ID,
Namespace: *n,
Reusable: reusable,
Ephemeral: ephemeral,
CreatedAt: &now,
Expiration: expiration,
}
h.db.Save(&k)
return &k, nil
}
// GetPreAuthKeys returns the list of PreAuthKeys for a namespace
func (h *Headscale) GetPreAuthKeys(namespaceName string) (*[]PreAuthKey, error) {
n, err := h.GetNamespace(namespaceName)
if err != nil {
return nil, err
}
keys := []PreAuthKey{}
if err := h.db.Preload("Namespace").Where(&PreAuthKey{NamespaceID: n.ID}).Find(&keys).Error; err != nil {
return nil, err
}
return &keys, nil
}
// GetPreAuthKey returns a PreAuthKey for a given key
func (h *Headscale) GetPreAuthKey(namespace string, key string) (*PreAuthKey, error) {
pak, err := h.checkKeyValidity(key)
if err != nil {
return nil, err
}
if pak.Namespace.Name != namespace {
return nil, errors.New("Namespace mismatch")
}
return pak, nil
}
// MarkExpirePreAuthKey marks a PreAuthKey as expired
func (h *Headscale) MarkExpirePreAuthKey(k *PreAuthKey) error {
if err := h.db.Model(&k).Update("Expiration", time.Now()).Error; err != nil {
return err
}
return nil
}
// checkKeyValidity does the heavy lifting for validation of the PreAuthKey coming from a node
// If returns no error and a PreAuthKey, it can be used
func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
pak := PreAuthKey{}
if result := h.db.Preload("Namespace").First(&pak, "key = ?", k); errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, errorAuthKeyNotFound
}
if pak.Expiration != nil && pak.Expiration.Before(time.Now()) {
return nil, errorAuthKeyExpired
}
if pak.Reusable || pak.Ephemeral { // we don't need to check if has been used before
return &pak, nil
}
machines := []Machine{}
if err := h.db.Preload("AuthKey").Where(&Machine{AuthKeyID: uint(pak.ID)}).Find(&machines).Error; err != nil {
return nil, err
}
if len(machines) != 0 {
return nil, errorAuthKeyNotReusableAlreadyUsed
}
// missing here validation on current usage
return &pak, nil
}
func (h *Headscale) generateKey() (string, error) {
size := 24
bytes := make([]byte, size)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}