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
package credentials
import (
"cmp"
"context"
"errors"
"fmt"
"strings"
"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
"github.com/johnandersen777/socialweb-computer-kcp/internal/component"
"github.com/johnandersen777/socialweb-computer-kcp/internal/kcplibs/common/ref"
"github.com/johnandersen777/socialweb-computer-kcp/internal/kcplibs/impl/kcpstore"
"github.com/johnandersen777/socialweb-computer-kcp/internal/vault/openbao"
)
type KCPStoreSource struct {
Store *kcpstore.Store
Client string
}
var _ openbao.MountSource = (*KCPStoreSource)(nil)
func (s *KCPStoreSource) Server(ctx context.Context, workspace, name string) (openbao.Server, error) {
return s.ServerForMount(ctx, workspace, name, "")
}
func (s *KCPStoreSource) ServerForMount(ctx context.Context, workspace, name, mount string) (openbao.Server, error) {
if s == nil || s.Store == nil {
return openbao.Server{}, errors.New("credentials: the OpenBaoServer source needs a kcp store")
}
if name == "" {
return openbao.Server{}, openbao.ErrNoServer
}
if workspace == "" {
workspace = "root"
}
obj, err := kcpstore.OfClusterScoped[v1alpha1.OpenBaoServer](s.Store, v1alpha1.OpenBaoServerGVR).Get(ctx, ref.New(workspace, "", name))
if err != nil {
return openbao.Server{}, fmt.Errorf("credentials: reading OpenBaoServer %s/%s: %w", workspace, name, err)
}
if obj == nil {
return openbao.Server{}, fmt.Errorf("credentials: OpenBaoServer %s/%s is not there", workspace, name)
}
return openbao.ServerOf(workspace, name, obj.Status, cmp.Or(openbao.ClientForMount(obj.Status.Clients, mount), s.Client))
}
type Clients struct {
Logins *openbao.Logins
Workspace string
}
var _ component.VaultClients = (*Clients)(nil)
func NewClients(store *kcpstore.Store, workspace, client string) (*Clients, error) {
if store == nil {
return nil, errors.New("credentials: the OpenBao vault clients need a kcp store")
}
return NewClientsWith(openbao.NewLogins(&KCPStoreSource{Store: store, Client: client}), workspace), nil
}
func NewClientsWith(logins *openbao.Logins, workspace string) *Clients {
if workspace == "" {
workspace = "root"
}
return &Clients{Logins: logins, Workspace: workspace}
}
func (c *Clients) Client(ctx context.Context, server string) (component.VaultClient, error) {
if strings.HasPrefix(strings.TrimSpace(server), "RemoteOpenBao/") {
return nil, fmt.Errorf("%w: %s", component.ErrRemoteVaultDisabled, server)
}
if c == nil || c.Logins == nil {
return nil, errors.New("credentials: the OpenBao vault clients have no logins")
}
workspace, name := openbao.ParseServer(server, c.Workspace)
if name == "" {
return nil, openbao.ErrNoServer
}
client, srv, err := c.Logins.Client(ctx, workspace, name)
if err != nil {
return nil, err
}
return &componentClient{client: client, mount: srv.KVMount}, nil
}
type componentClient struct {
client *openbao.Client
mount string
}
func (c *componentClient) mountOr(mount string) string {
if mount = strings.Trim(mount, "/"); mount != "" {
return mount
}
return strings.Trim(c.mount, "/")
}
func kvDataPath(mount, path string) string {
path = strings.Trim(path, "/")
if strings.HasPrefix(path, mount+"/data/") {
return path
}
return mount + "/data/" + path
}
func (c *componentClient) ReadKV(ctx context.Context, mount, path string) (map[string]any, error) {
return c.client.GetKV(ctx, kvDataPath(c.mountOr(mount), path))
}
func (c *componentClient) WriteKV(ctx context.Context, mount, path string, data map[string]any) error {
if data == nil {
data = map[string]any{}
}
return c.client.PutKV(ctx, kvDataPath(c.mountOr(mount), path), data)
}
func (c *componentClient) DeleteKV(ctx context.Context, mount, path string) error {
mount = c.mountOr(mount)
path = strings.TrimPrefix(strings.Trim(path, "/"), mount+"/metadata/")
return c.client.Delete(ctx, mount+"/metadata/"+path)
}
func (c *componentClient) IssueCertificate(ctx context.Context, req component.CertificateRequest) (component.Certificate, error) {
if strings.TrimSpace(req.Mount) == "" || strings.TrimSpace(req.Role) == "" || strings.TrimSpace(req.CommonName) == "" {
return component.Certificate{}, errors.New("credentials: issuing a certificate needs a PKI mount, a role and a common name")
}
in := openbao.CertRequest{CommonName: req.CommonName, AltNames: append([]string(nil), req.AltNames...), IPSANs: append([]string(nil), req.IPSANs...)}
if req.TTL > 0 {
in.TTL = req.TTL.String()
}
out, err := c.client.IssueCert(ctx, req.Mount, req.Role, in)
if err != nil {
return component.Certificate{}, err
}
certificate := component.Certificate{
CertificatePEM: []byte(out.Certificate),
PrivateKeyPEM: []byte(out.PrivateKey),
IssuingCAPEM: []byte(out.IssuingCA),
Serial: out.Serial,
}
for _, entry := range out.CAChain {
certificate.CAChainPEM = append(certificate.CAChainPEM, []byte(entry))
}
return certificate, nil
}