Skip to content

Commit 7c71158

Browse files
committed
testcases for vault as ca
Signed-off-by: Aditya Joshi <[email protected]>
1 parent 819a62a commit 7c71158

File tree

2 files changed

+342
-3
lines changed

2 files changed

+342
-3
lines changed

controllers/certs_vault/provision_certs.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ func GetCAInfo(params GetCAInfoRequest) (*lib.GetCAInfoResponse, error) {
371371
return nil, fmt.Errorf("GetCAInfo functionality not implemented for Vault yet")
372372
}
373373

374-
func ReenrollUser(clientSet *kubernetes.Clientset, spec *hlfv1alpha1.VaultSpecConf, request *hlfv1alpha1.VaultPKICertificateRequest, params ReenrollUserRequest, certPem string, ecdsaKey *ecdsa.PrivateKey) (*x509.Certificate, *x509.Certificate, error) {
374+
func ReenrollUser(clientSet kubernetes.Interface, spec *hlfv1alpha1.VaultSpecConf, request *hlfv1alpha1.VaultPKICertificateRequest, params ReenrollUserRequest, certPem string, ecdsaKey *ecdsa.PrivateKey) (*x509.Certificate, *x509.Certificate, error) {
375375
vaultClient, err := GetClient(spec, clientSet)
376376
if err != nil {
377377
return nil, nil, err
@@ -451,7 +451,7 @@ func ReenrollUser(clientSet *kubernetes.Clientset, spec *hlfv1alpha1.VaultSpecCo
451451

452452
return cert, caCert, nil
453453
}
454-
func EnrollUser(clientSet *kubernetes.Clientset, vaultConf *hlfv1alpha1.VaultSpecConf, request *hlfv1alpha1.VaultPKICertificateRequest, params EnrollUserRequest) (*x509.Certificate, *ecdsa.PrivateKey, *x509.Certificate, error) {
454+
func EnrollUser(clientSet kubernetes.Interface, vaultConf *hlfv1alpha1.VaultSpecConf, request *hlfv1alpha1.VaultPKICertificateRequest, params EnrollUserRequest) (*x509.Certificate, *ecdsa.PrivateKey, *x509.Certificate, error) {
455455
// Use the provided VaultSpecConf to get a client
456456
vaultClient, err := GetClient(vaultConf, clientSet)
457457
if err != nil {
@@ -561,7 +561,7 @@ func convertToVaultConfig(params FabricCAParams) *hlfv1alpha1.VaultSpecConf {
561561
}
562562
}
563563

564-
func GetClient(spec *hlfv1alpha1.VaultSpecConf, clientset *kubernetes.Clientset) (*vault.Client, error) {
564+
func GetClient(spec *hlfv1alpha1.VaultSpecConf, clientset kubernetes.Interface) (*vault.Client, error) {
565565
// Configure Vault client
566566
vaultConfig := vault.DefaultConfiguration()
567567
vaultConfig.Address = spec.URL
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
package certs_vault
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
"fmt"
7+
"github.com/hashicorp/vault-client-go"
8+
"github.com/hashicorp/vault-client-go/schema"
9+
hlfv1alpha1 "github.com/kfsoftware/hlf-operator/pkg/apis/hlf.kungfusoftware.es/v1alpha1"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"github.com/testcontainers/testcontainers-go"
13+
"github.com/testcontainers/testcontainers-go/wait"
14+
corev1 "k8s.io/api/core/v1"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/client-go/kubernetes"
17+
"k8s.io/client-go/kubernetes/fake"
18+
"testing"
19+
"time"
20+
)
21+
22+
const (
23+
vaultImage = "hashicorp/vault:1.7.2"
24+
vaultPort = "8200"
25+
vaultRootToken = "test-root-token"
26+
vaultTokenSecret = "vault-token"
27+
vaultNamespace = "default"
28+
pkiMountPath = "test"
29+
caIssuerName = "test-ca"
30+
caCommonName = "Test CA"
31+
caTTL = "87600h"
32+
roleName = "fabric"
33+
roleMaxTTL = "87600h"
34+
roleKeyType = "ec"
35+
roleKeyBits = 256
36+
roleOU = "peer"
37+
roleOrg = "Org1MSP"
38+
certTTL = "24h"
39+
certUser = "testUser"
40+
certUserCN = "testUserCN"
41+
certHost = "localhost"
42+
certMSPID = "Org1MSP"
43+
startupTimeout = 60 * time.Second
44+
internalSleep = 2 * time.Second
45+
requestTimeout = 30 * time.Second
46+
certExpiryMargin = time.Hour
47+
)
48+
49+
type VaultContainer struct {
50+
testcontainers.Container
51+
Address string
52+
RootToken string
53+
}
54+
55+
func setupVaultDev(ctx context.Context) (*VaultContainer, error) {
56+
req := testcontainers.ContainerRequest{
57+
Image: vaultImage,
58+
ExposedPorts: []string{vaultPort + "/tcp"},
59+
Env: map[string]string{
60+
"VAULT_DEV_ROOT_TOKEN_ID": vaultRootToken,
61+
},
62+
Cmd: []string{
63+
"server",
64+
"-dev",
65+
"-dev-root-token-id=" + vaultRootToken,
66+
"-dev-listen-address=0.0.0.0:" + vaultPort,
67+
},
68+
WaitingFor: wait.ForHTTP("/v1/sys/health").
69+
WithPort(vaultPort + "/tcp").
70+
WithStartupTimeout(startupTimeout).
71+
WithStatusCodeMatcher(func(code int) bool {
72+
return code == 200 || code == 429
73+
}),
74+
}
75+
76+
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
77+
ContainerRequest: req,
78+
Started: true,
79+
})
80+
if err != nil {
81+
return nil, fmt.Errorf("starting vault container: %w", err)
82+
}
83+
84+
mappedPort, err := container.MappedPort(ctx, vaultPort)
85+
if err != nil {
86+
return nil, fmt.Errorf("getting mapped port: %w", err)
87+
}
88+
89+
host, err := container.Host(ctx)
90+
if err != nil {
91+
return nil, fmt.Errorf("getting container host: %w", err)
92+
}
93+
94+
address := fmt.Sprintf("http://%s:%s", host, mappedPort.Port())
95+
time.Sleep(internalSleep)
96+
97+
return &VaultContainer{
98+
Container: container,
99+
Address: address,
100+
RootToken: vaultRootToken,
101+
}, nil
102+
}
103+
104+
func TestEnrollUser(t *testing.T) {
105+
106+
ctx := context.Background()
107+
108+
vaultContainer, err := setupVaultDev(ctx)
109+
require.NoError(t, err, "Failed to setup vault")
110+
defer func() {
111+
assert.NoError(t, vaultContainer.Terminate(ctx), "Failed to terminate container")
112+
}()
113+
114+
vaultClient, err := vault.New(
115+
vault.WithAddress(vaultContainer.Address),
116+
vault.WithRequestTimeout(requestTimeout),
117+
)
118+
require.NoError(t, err, "Failed to create Vault client")
119+
err = vaultClient.SetToken(vaultContainer.RootToken)
120+
require.NoError(t, err, "Failed to set Vault token")
121+
122+
err = EnablePKI(ctx, vaultClient, pkiMountPath, caTTL)
123+
require.NoError(t, err, "Failed to enable PKI")
124+
125+
err = CreateVaultIssuer(ctx, vaultClient, pkiMountPath, caIssuerName, map[string]interface{}{
126+
"key_type": roleKeyType,
127+
"key_bits": roleKeyBits,
128+
"ttl": caTTL,
129+
"common_name": caCommonName,
130+
})
131+
require.NoError(t, err, "Failed to create Vault issuer")
132+
133+
err = CreateVaultRole(ctx, vaultClient, roleName, map[string]interface{}{
134+
"issuer_ref": caIssuerName,
135+
"allow_subdomains": true,
136+
"allow_any_name": true,
137+
"max_ttl": roleMaxTTL,
138+
"key_type": roleKeyType,
139+
"key_bits": roleKeyBits,
140+
"ou": roleOU,
141+
"organization": roleOrg,
142+
})
143+
require.NoError(t, err, "Failed to create Vault role")
144+
145+
clientSet := GetFakeClientsetWithVaultToken()
146+
147+
vaultConf := &hlfv1alpha1.VaultSpecConf{
148+
URL: vaultContainer.Address,
149+
TLSSkipVerify: true,
150+
TokenSecretRef: &hlfv1alpha1.VaultSecretRef{
151+
Name: vaultTokenSecret,
152+
Namespace: vaultNamespace,
153+
Key: "token",
154+
},
155+
}
156+
request := &hlfv1alpha1.VaultPKICertificateRequest{
157+
PKI: pkiMountPath,
158+
Role: roleName,
159+
TTL: certTTL,
160+
}
161+
params := EnrollUserRequest{
162+
MSPID: certMSPID,
163+
User: certUser,
164+
Hosts: []string{certHost},
165+
CN: certUserCN,
166+
}
167+
168+
cert, privateKey, caCert, err := EnrollUser(clientSet, vaultConf, request, params)
169+
require.NoError(t, err, "Failed to enroll user")
170+
assert.NotNil(t, cert, "Certificate should not be nil")
171+
assert.NotNil(t, privateKey, "Private key should not be nil")
172+
assert.NotNil(t, caCert, "CA certificate should not be nil")
173+
expiry := cert.NotAfter
174+
expectedExpiry := time.Now().Add(24 * time.Hour)
175+
assert.WithinDuration(t, expectedExpiry, expiry, certExpiryMargin, "Certificate expiry does not match expected TTL")
176+
}
177+
178+
func TestReenrollUser(t *testing.T) {
179+
ctx := context.Background()
180+
vaultContainer, err := setupVaultDev(ctx)
181+
require.NoError(t, err, "Failed to setup vault")
182+
defer func() {
183+
assert.NoError(t, vaultContainer.Terminate(ctx), "Failed to terminate container")
184+
}()
185+
186+
vaultClient, err := vault.New(
187+
vault.WithAddress(vaultContainer.Address),
188+
vault.WithRequestTimeout(requestTimeout),
189+
)
190+
require.NoError(t, err, "Failed to create Vault client")
191+
err = vaultClient.SetToken(vaultContainer.RootToken)
192+
require.NoError(t, err, "Failed to set Vault token")
193+
194+
err = EnablePKI(ctx, vaultClient, pkiMountPath, caTTL)
195+
require.NoError(t, err, "Failed to enable PKI")
196+
197+
err = CreateVaultIssuer(ctx, vaultClient, pkiMountPath, caIssuerName, map[string]interface{}{
198+
"key_type": roleKeyType,
199+
"key_bits": roleKeyBits,
200+
"ttl": caTTL,
201+
"common_name": caCommonName,
202+
})
203+
require.NoError(t, err, "Failed to create Vault issuer")
204+
205+
err = CreateVaultRole(ctx, vaultClient, roleName, map[string]interface{}{
206+
"issuer_ref": caIssuerName,
207+
"allow_subdomains": true,
208+
"allow_any_name": true,
209+
"max_ttl": roleMaxTTL,
210+
"key_type": roleKeyType,
211+
"key_bits": roleKeyBits,
212+
"ou": roleOU,
213+
"organization": roleOrg,
214+
})
215+
require.NoError(t, err, "Failed to create Vault role")
216+
217+
clientSet := GetFakeClientsetWithVaultToken()
218+
vaultConf := &hlfv1alpha1.VaultSpecConf{
219+
URL: vaultContainer.Address,
220+
TLSSkipVerify: true,
221+
TokenSecretRef: &hlfv1alpha1.VaultSecretRef{
222+
Name: vaultTokenSecret,
223+
Namespace: vaultNamespace,
224+
Key: "token",
225+
},
226+
}
227+
request := &hlfv1alpha1.VaultPKICertificateRequest{
228+
PKI: pkiMountPath,
229+
Role: roleName,
230+
TTL: certTTL,
231+
}
232+
enrollUserRequest := EnrollUserRequest{
233+
MSPID: certMSPID,
234+
User: certUser,
235+
Hosts: []string{certHost},
236+
CN: certUserCN,
237+
}
238+
239+
cert1, privateKey1, caCert1, err := EnrollUser(clientSet, vaultConf, request, enrollUserRequest)
240+
require.NoError(t, err, "Failed to enroll user")
241+
require.NotNil(t, cert1, "Certificate should not be nil")
242+
require.NotNil(t, privateKey1, "Private key should not be nil")
243+
require.NotNil(t, caCert1, "CA certificate should not be nil")
244+
245+
certPEM1 := fmt.Sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n",
246+
base64.StdEncoding.EncodeToString(cert1.Raw))
247+
248+
reenrollUserRequest := ReenrollUserRequest{
249+
MSPID: certMSPID,
250+
EnrollID: certUserCN,
251+
Hosts: []string{certHost},
252+
CN: certUserCN,
253+
}
254+
255+
cert2, caCert2, err := ReenrollUser(clientSet, vaultConf, request, reenrollUserRequest, certPEM1, privateKey1)
256+
require.NoError(t, err, "Failed to reenroll user")
257+
require.NotNil(t, cert2, "Reenrolled certificate should not be nil")
258+
require.NotNil(t, caCert2, "Reenrolled CA certificate should not be nil")
259+
260+
assert.Equal(t, cert1.PublicKey, cert2.PublicKey, "Private keys should be the same after reenrollment")
261+
}
262+
263+
func GetFakeClientsetWithVaultToken() kubernetes.Interface {
264+
secret := &corev1.Secret{
265+
ObjectMeta: metav1.ObjectMeta{
266+
Name: vaultTokenSecret,
267+
Namespace: vaultNamespace,
268+
},
269+
Type: corev1.SecretTypeOpaque,
270+
Data: map[string][]byte{
271+
"token": []byte(vaultRootToken),
272+
},
273+
}
274+
return fake.NewClientset(secret)
275+
}
276+
277+
// CreateVaultRole creates a new role in Vault PKI with the given parameters
278+
func CreateVaultRole(ctx context.Context, vaultClient *vault.Client, roleName string, params map[string]interface{}) error {
279+
if roleName == "" {
280+
return fmt.Errorf("roleName cannot be empty")
281+
}
282+
if params == nil {
283+
return fmt.Errorf("params cannot be nil")
284+
}
285+
286+
rolePath := fmt.Sprintf("%s/roles/%s", pkiMountPath, roleName)
287+
_, err := vaultClient.Write(ctx, rolePath, params)
288+
if err != nil {
289+
return fmt.Errorf("failed to create role in Vault: %w", err)
290+
}
291+
292+
return nil
293+
}
294+
295+
// EnablePKI enables the PKI secrets engine at the given mount path
296+
func EnablePKI(ctx context.Context, vaultClient *vault.Client, mountPath string, maxLeaseTTL string) error {
297+
if mountPath == "" {
298+
return fmt.Errorf("mountPath cannot be empty")
299+
}
300+
if maxLeaseTTL == "" {
301+
maxLeaseTTL = "87600h"
302+
}
303+
304+
req := schema.MountsEnableSecretsEngineRequest{
305+
Type: "pki",
306+
Config: map[string]interface{}{
307+
"max_lease_ttl": maxLeaseTTL,
308+
},
309+
}
310+
311+
_, err := vaultClient.System.MountsEnableSecretsEngine(ctx, mountPath, req)
312+
if err != nil {
313+
return fmt.Errorf("failed to enable PKI engine: %w", err)
314+
}
315+
316+
return nil
317+
}
318+
319+
// CreateVaultIssuer creates a new issuer (CA) in Vault PKI
320+
func CreateVaultIssuer(ctx context.Context, vaultClient *vault.Client, pki, issuerName string, params map[string]interface{}) error {
321+
if pki == "" {
322+
return fmt.Errorf("pki mount path cannot be empty")
323+
}
324+
if issuerName == "" {
325+
return fmt.Errorf("issuerName cannot be empty")
326+
}
327+
if params == nil {
328+
return fmt.Errorf("params cannot be nil")
329+
}
330+
331+
issuerPath := fmt.Sprintf("%s/root/generate/internal", pki)
332+
params["issuer_name"] = issuerName
333+
_, err := vaultClient.Write(ctx, issuerPath, params)
334+
if err != nil {
335+
return fmt.Errorf("failed to create issuer in Vault: %w", err)
336+
}
337+
338+
return nil
339+
}

0 commit comments

Comments
 (0)