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
package identitybundle
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"os"
"path/filepath"
"time"
)
var Paths = []string{
"tls/server-ca.crt",
"tls/server-ca.key",
"tls/client-ca.crt",
"tls/client-ca.key",
"tls/request-header-ca.crt",
"tls/request-header-ca.key",
"tls/etcd/server-ca.crt",
"tls/etcd/server-ca.key",
"tls/etcd/peer-ca.crt",
"tls/etcd/peer-ca.key",
"tls/service.key",
"cred/passwd",
"cred/ipsec.psk",
}
type Entry struct {
Path string
Bytes []byte
ModTime time.Time
}
type Bundle struct {
Entries []Entry
}
var ErrIncomplete = errors.New("identitybundle: incomplete")
var ErrMismatch = errors.New("identitybundle: mismatch")
func Harvest(dataDir string) (*Bundle, error) {
b := &Bundle{Entries: make([]Entry, 0, len(Paths))}
for _, rel := range Paths {
full := filepath.Join(dataDir, rel)
info, err := os.Stat(full)
if err != nil {
return nil, fmt.Errorf("%w: %s: %w", ErrIncomplete, rel, err)
}
data, err := os.ReadFile(full)
if err != nil {
return nil, fmt.Errorf("%w: %s: %w", ErrIncomplete, rel, err)
}
b.Entries = append(b.Entries, Entry{Path: rel, Bytes: data, ModTime: info.ModTime()})
}
return b, nil
}
func (b *Bundle) Verify(dataDir string) error {
if len(b.Entries) != len(Paths) {
return fmt.Errorf("%w: bundle has %d entries, want %d", ErrIncomplete, len(b.Entries), len(Paths))
}
for _, e := range b.Entries {
got, err := os.ReadFile(filepath.Join(dataDir, e.Path))
if err != nil {
return fmt.Errorf("%w: %s: %w", ErrMismatch, e.Path, err)
}
if !equal(got, e.Bytes) {
return fmt.Errorf("%w: %s differs (on disk %s, expected %s)",
ErrMismatch, e.Path, Sum(got), Sum(e.Bytes))
}
}
return nil
}
func (b *Bundle) Seed(dataDir string) error {
if len(b.Entries) != len(Paths) {
return fmt.Errorf("%w: bundle has %d entries, want %d", ErrIncomplete, len(b.Entries), len(Paths))
}
for _, e := range b.Entries {
full := filepath.Join(dataDir, e.Path)
if err := os.MkdirAll(filepath.Dir(full), 0o700); err != nil {
return fmt.Errorf("identitybundle: mkdir for %s: %w", e.Path, err)
}
if err := os.WriteFile(full, e.Bytes, 0o600); err != nil {
return fmt.Errorf("identitybundle: write %s: %w", e.Path, err)
}
if err := os.Chtimes(full, e.ModTime, e.ModTime); err != nil {
return fmt.Errorf("identitybundle: chtimes %s: %w", e.Path, err)
}
}
return b.Verify(dataDir)
}
func Sum(b []byte) string {
h := sha256.Sum256(b)
return hex.EncodeToString(h[:])
}
const ClusterUIDFile = "cluster-uid"
var ErrEarlierCreation = errors.New("identitybundle: identity bundle belongs to an earlier creation of this cluster")
func ReadClusterUID(dir string) (string, error) {
raw, err := os.ReadFile(filepath.Join(dir, ClusterUIDFile))
if errors.Is(err, os.ErrNotExist) {
return "", nil
}
if err != nil {
return "", fmt.Errorf("identitybundle: reading %s: %w", ClusterUIDFile, err)
}
return string(raw), nil
}
func WriteClusterUID(dir, uid string) error {
if err := os.MkdirAll(dir, 0o700); err != nil {
return fmt.Errorf("identitybundle: mkdir for %s: %w", ClusterUIDFile, err)
}
if err := os.WriteFile(filepath.Join(dir, ClusterUIDFile), []byte(uid), 0o600); err != nil {
return fmt.Errorf("identitybundle: write %s: %w", ClusterUIDFile, err)
}
return nil
}
func BindClusterUID(dir, uid string) (adopted bool, err error) {
have, err := ReadClusterUID(dir)
if err != nil {
return false, err
}
switch have {
case uid:
return false, nil
case "":
return true, WriteClusterUID(dir, uid)
}
return false, fmt.Errorf("%w: it is bound to cluster uid %s and this creation is %s", ErrEarlierCreation, have, uid)
}
func equal(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}