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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package clusterprovider
import (
"sort"
"strings"
"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
"github.com/johnandersen777/socialweb-computer-kcp/internal/clusterlink"
"github.com/johnandersen777/socialweb-computer-kcp/internal/contract/clusterhome"
"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/clock"
"github.com/johnandersen777/socialweb-computer-kcp/internal/vaultcert"
"github.com/johnandersen777/socialweb-computer-kcp/internal/vaultlayout"
)
const (
VaultClientPolicy = "clusterprovider"
VaultGatewayClient = "gateway-kubeconfig"
VaultGatewayClientPolicy = "gateway-kubeconfig"
vaultAppRoleMount = "approle"
)
func clusterProviderRules() []v1alpha1.OpenBaoPolicyRule {
cu := []string{"create", "update"}
cru := []string{"create", "read", "update"}
return []v1alpha1.OpenBaoPolicyRule{
{Path: strings.TrimSuffix(vaultlayout.MountKVCluster, "/") + "/data/k3s/*", Capabilities: cru},
{Path: vaultlayout.NodesKVMount + "/data/*", Capabilities: cu},
{Path: clusterlink.Mount + "/data/*", Capabilities: cru},
{Path: VaultPKIPath + "/config/ca", Capabilities: cu},
{Path: VaultPKIPath + "/issuer/*", Capabilities: cu},
{Path: VaultPKIPath + "/issue/" + VaultPKIRoleTenant, Capabilities: cu},
{Path: VaultPKIPath + "/issue/" + vaultcert.PKIRoleNode, Capabilities: cu},
}
}
func gatewayKubeconfigRules() []v1alpha1.OpenBaoPolicyRule {
cu := []string{"create", "update"}
return []v1alpha1.OpenBaoPolicyRule{
{Path: VaultPKIPath + "/config/ca", Capabilities: cu},
{Path: VaultPKIPath + "/issuer/*", Capabilities: cu},
{Path: VaultPKIPath + "/issue/" + VaultPKIRoleKubeconfig, Capabilities: cu},
{Path: VaultPKIPath + "/issue/" + VaultPKIRoleKubeconfigNoExpiry, Capabilities: cu},
{Path: VaultPKIPath + "/revoke", Capabilities: cu},
{Path: VaultPKIPath + "/certs", Capabilities: []string{"list"}},
{Path: VaultPKIPath + "/certs/", Capabilities: []string{"list"}},
{Path: VaultPKIPath + "/certs/revoked", Capabilities: []string{"list"}},
{Path: VaultPKIPath + "/certs/revoked/", Capabilities: []string{"list"}},
{Path: VaultPKIPath + "/cert/*", Capabilities: []string{"read"}},
}
}
func layoutPKIRole(r vaultlayout.PKIRole) v1alpha1.OpenBaoPKIRole {
return v1alpha1.OpenBaoPKIRole{Mount: VaultPKIPath, Name: r.Name, AllowedDomains: r.AllowedDomains, AllowSubdomains: r.AllowSubdomains,
AllowBareDomains: r.AllowBareDomains, AllowIPSANs: r.AllowIPSANs, EnforceHostnames: r.EnforceHostnames, RequireCN: r.RequireCN,
ClientFlag: r.ClientAuth, ServerFlag: r.ServerAuth, KeyType: vaultKeyType(r.KeyType), KeyBits: int32(r.KeyBits),
KeyUsage: r.KeyUsage, TTL: clock.DurationString(r.TTL), MaxTTL: clock.DurationString(r.MaxTTL), NoStore: true}
}
func staticClusterDeclared() *v1alpha1.OpenBaoServerDeclared {
layout := vaultlayout.Default()
d := &v1alpha1.OpenBaoServerDeclared{}
for _, m := range layout.Mounts {
if m.Vault == vaultlayout.Cluster {
d.SecretMounts = append(d.SecretMounts, v1alpha1.OpenBaoSecretMount{Path: strings.Trim(m.Path, "/"), Type: string(m.Kind)})
}
}
d.SecretMounts = append(d.SecretMounts,
v1alpha1.OpenBaoSecretMount{Path: VaultPKIPath, Type: string(vaultlayout.MountPKI)},
v1alpha1.OpenBaoSecretMount{Path: vaultlayout.NodesKVMount, Type: string(vaultlayout.MountKV)},
v1alpha1.OpenBaoSecretMount{Path: clusterlink.Mount, Type: string(vaultlayout.MountKV)})
d.AuthMounts = append(d.AuthMounts, v1alpha1.OpenBaoAuthMount{Path: vaultAppRoleMount, Type: "approle"})
for _, p := range layout.Policies {
if p.Vault == vaultlayout.Cluster {
d.Policies = append(d.Policies, v1alpha1.OpenBaoPolicy{Name: p.Name, HCL: p.HCL})
}
}
d.Policies = append(d.Policies,
v1alpha1.OpenBaoPolicy{Name: vaultlayout.PolicyESO, HCL: vaultlayout.ESOCertPolicyHCL()},
v1alpha1.OpenBaoPolicy{Name: VaultClientPolicy, Rules: clusterProviderRules()},
v1alpha1.OpenBaoPolicy{Name: VaultGatewayClientPolicy, Rules: gatewayKubeconfigRules()})
d.CertRoles = append(d.CertRoles, v1alpha1.OpenBaoCertRole{Mount: vaultlayout.CertAuthPath, Name: vaultlayout.RoleESO,
CommonNames: []string{vaultlayout.RoleESO}, CAFromPKI: VaultPKIPath, Policies: []string{vaultlayout.PolicyESO},
TokenTTL: vaultcert.NodeTokenTTL, TokenMaxTTL: vaultcert.NodeTokenMax, NoDefaultPolicy: true})
for _, r := range layout.AppRoles {
if r.Vault == vaultlayout.Cluster {
d.AppRoles = append(d.AppRoles, v1alpha1.OpenBaoAppRole{Mount: vaultAppRoleMount, Name: r.Name, Policies: r.Policies,
TokenTTL: clock.DurationString(r.TokenTTL), TokenMaxTTL: clock.DurationString(r.TokenMaxTTL), SecretIDTTL: clock.DurationString(r.SecretIDTTL)})
}
}
for _, r := range layout.PKIRoles {
d.PKIRoles = append(d.PKIRoles, layoutPKIRole(r))
}
d.PKIRoles = append(d.PKIRoles,
v1alpha1.OpenBaoPKIRole{Mount: VaultPKIPath, Name: VaultPKIRoleTenant, AllowAnyName: true, ClientFlag: true, RequireCN: true,
KeyType: "ec", KeyBits: 256, Organization: []string{SuperuserOrganization}, TTL: "8760h", MaxTTL: "87600h", NoStore: true},
v1alpha1.OpenBaoPKIRole{Mount: VaultPKIPath, Name: vaultcert.PKIRoleNode, AllowAnyName: true, ClientFlag: true, RequireCN: true,
KeyType: "ec", KeyBits: 256, TTL: clock.DurationString(vaultcert.NodeCertTTL), MaxTTL: clock.DurationString(vaultcert.NodeMaxTTL), NoStore: true},
v1alpha1.OpenBaoPKIRole{Mount: VaultPKIPath, Name: VaultPKIRoleKubeconfig, AllowAnyName: true, EnforceHostnames: false,
ClientFlag: true, ServerFlag: false, RequireCN: true, KeyType: "ec", KeyBits: 256, KeyUsage: []string{"DigitalSignature"},
Organization: []string{clusterhome.OwnersGroup}, CNValidations: []string{"disabled"}, TTL: "8h", MaxTTL: "24h"},
v1alpha1.OpenBaoPKIRole{Mount: VaultPKIPath, Name: VaultPKIRoleKubeconfigNoExpiry, AllowAnyName: true, EnforceHostnames: false,
ClientFlag: true, ServerFlag: false, RequireCN: true, KeyType: "ec", KeyBits: 256, KeyUsage: []string{"DigitalSignature"},
Organization: []string{clusterhome.OwnersGroup}, OrganizationalUnit: []string{NoExpiryOrganizationalUnit},
CNValidations: []string{"disabled"}})
d.PKITidy = append(d.PKITidy, v1alpha1.OpenBaoPKITidy{Mount: VaultPKIPath})
return d
}
func ClusterVaultDeclared(prev *v1alpha1.OpenBaoServerDeclared) *v1alpha1.OpenBaoServerDeclared {
d := staticClusterDeclared()
setVaultNodes(d, vaultNodes(prev))
return d
}
func grantUsable(g *v1alpha1.NodeClaimGrant) bool {
return g != nil && g.Issuer != "" && g.Subject != "" && g.Audience != ""
}
func vaultNodes(d *v1alpha1.OpenBaoServerDeclared) map[string]*v1alpha1.NodeClaimGrant {
out := map[string]*v1alpha1.NodeClaimGrant{}
if d == nil {
return out
}
issuers := map[string]string{}
for _, m := range d.AuthMounts {
if vaultlayout.IsNodeJWTMount(m.Path) && m.JWT != nil {
issuers[m.Path] = m.JWT.DiscoveryURL
}
}
for _, p := range d.Policies {
if node, ok := vaultlayout.NodeFromJWTPolicy(p.Name); ok {
out[node] = nil
}
}
for _, r := range d.JWTRoles {
if _, ok := out[r.Name]; !ok || issuers[r.Mount] == "" || len(r.BoundAudiences) == 0 {
continue
}
out[r.Name] = &v1alpha1.NodeClaimGrant{Issuer: issuers[r.Mount], Subject: r.BoundSubject, Audience: r.BoundAudiences[0]}
}
return out
}
func setVaultNodes(d *v1alpha1.OpenBaoServerDeclared, nodes map[string]*v1alpha1.NodeClaimGrant) {
keep := d.Policies[:0]
for _, p := range d.Policies {
if _, ok := vaultlayout.NodeFromJWTPolicy(p.Name); !ok {
keep = append(keep, p)
}
}
d.Policies = keep
auths := d.AuthMounts[:0]
for _, m := range d.AuthMounts {
if !vaultlayout.IsNodeJWTMount(m.Path) {
auths = append(auths, m)
}
}
d.AuthMounts = auths
d.JWTRoles = nil
kv := d.KV[:0]
for _, s := range d.KV {
if s.Mount != vaultlayout.NodesKVMount {
kv = append(kv, s)
}
}
d.KV = kv
names := make([]string, 0, len(nodes))
for n := range nodes {
names = append(names, n)
}
sort.Strings(names)
mounts := map[string]string{}
for _, n := range names {
d.Policies = append(d.Policies, v1alpha1.OpenBaoPolicy{Name: vaultlayout.NodeJWTPolicyName(n), HCL: vaultlayout.NodeJWTPolicyHCL(n)})
g := nodes[n]
if !grantUsable(g) {
continue
}
mount := vaultlayout.NodeJWTMount(g.Issuer)
mounts[mount] = g.Issuer
d.JWTRoles = append(d.JWTRoles, v1alpha1.OpenBaoJWTRole{Mount: mount, Name: vaultlayout.NodeJWTRoleName(n),
RoleType: vaultlayout.NodeJWTRoleType, BoundSubject: g.Subject, BoundAudiences: []string{g.Audience},
UserClaim: vaultlayout.NodeJWTUserClaim, Policies: []string{vaultlayout.NodeJWTPolicyName(n)}, TokenTTL: vaultlayout.NodeJWTTokenTTL})
}
mountNames := make([]string, 0, len(mounts))
for m := range mounts {
mountNames = append(mountNames, m)
}
sort.Strings(mountNames)
for _, m := range mountNames {
d.AuthMounts = append(d.AuthMounts, v1alpha1.OpenBaoAuthMount{Path: m, Type: "jwt",
JWT: &v1alpha1.OpenBaoJWTConfig{DiscoveryURL: mounts[m], BoundIssuer: mounts[m]}})
}
d.KV = append(d.KV, v1alpha1.OpenBaoKVScope{Mount: vaultlayout.NodesKVMount, Keys: names})
}