commit
d61cd98a3e
@ -0,0 +1,355 @@
|
|||||||
|
package vaultcas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/cas/apiv1"
|
||||||
|
|
||||||
|
vault "github.com/hashicorp/vault/api"
|
||||||
|
auth "github.com/hashicorp/vault/api/auth/approle"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiv1.Register(apiv1.VaultCAS, func(ctx context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) {
|
||||||
|
return New(ctx, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// VaultOptions defines the configuration options added using the
|
||||||
|
// apiv1.Options.Config field.
|
||||||
|
type VaultOptions struct {
|
||||||
|
PKI string `json:"pki,omitempty"`
|
||||||
|
PKIRoleDefault string `json:"pkiRoleDefault,omitempty"`
|
||||||
|
PKIRoleRSA string `json:"pkiRoleRSA,omitempty"`
|
||||||
|
PKIRoleEC string `json:"pkiRoleEC,omitempty"`
|
||||||
|
PKIRoleEd25519 string `json:"pkiRoleEd25519,omitempty"`
|
||||||
|
RoleID string `json:"roleID,omitempty"`
|
||||||
|
SecretID auth.SecretID `json:"secretID,omitempty"`
|
||||||
|
AppRole string `json:"appRole,omitempty"`
|
||||||
|
IsWrappingToken bool `json:"isWrappingToken,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VaultCAS implements a Certificate Authority Service using Hashicorp Vault.
|
||||||
|
type VaultCAS struct {
|
||||||
|
client *vault.Client
|
||||||
|
config VaultOptions
|
||||||
|
fingerprint string
|
||||||
|
}
|
||||||
|
|
||||||
|
type certBundle struct {
|
||||||
|
leaf *x509.Certificate
|
||||||
|
intermediates []*x509.Certificate
|
||||||
|
root *x509.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new CertificateAuthorityService implementation
|
||||||
|
// using Hashicorp Vault
|
||||||
|
func New(ctx context.Context, opts apiv1.Options) (*VaultCAS, error) {
|
||||||
|
if opts.CertificateAuthority == "" {
|
||||||
|
return nil, errors.New("vaultCAS 'certificateAuthority' cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.CertificateAuthorityFingerprint == "" {
|
||||||
|
return nil, errors.New("vaultCAS 'certificateAuthorityFingerprint' cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
vc, err := loadOptions(opts.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := vault.DefaultConfig()
|
||||||
|
config.Address = opts.CertificateAuthority
|
||||||
|
|
||||||
|
client, err := vault.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize vault client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var appRoleAuth *auth.AppRoleAuth
|
||||||
|
if vc.IsWrappingToken {
|
||||||
|
appRoleAuth, err = auth.NewAppRoleAuth(
|
||||||
|
vc.RoleID,
|
||||||
|
&vc.SecretID,
|
||||||
|
auth.WithWrappingToken(),
|
||||||
|
auth.WithMountPath(vc.AppRole),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
appRoleAuth, err = auth.NewAppRoleAuth(
|
||||||
|
vc.RoleID,
|
||||||
|
&vc.SecretID,
|
||||||
|
auth.WithMountPath(vc.AppRole),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize AppRole auth method: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authInfo, err := client.Auth().Login(ctx, appRoleAuth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to login to AppRole auth method: %w", err)
|
||||||
|
}
|
||||||
|
if authInfo == nil {
|
||||||
|
return nil, errors.New("no auth info was returned after login")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VaultCAS{
|
||||||
|
client: client,
|
||||||
|
config: *vc,
|
||||||
|
fingerprint: opts.CertificateAuthorityFingerprint,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCertificate signs a new certificate using Hashicorp Vault.
|
||||||
|
func (v *VaultCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
|
||||||
|
switch {
|
||||||
|
case req.CSR == nil:
|
||||||
|
return nil, errors.New("createCertificate `csr` cannot be nil")
|
||||||
|
case req.Lifetime == 0:
|
||||||
|
return nil, errors.New("createCertificate `lifetime` cannot be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, chain, err := v.createCertificate(req.CSR, req.Lifetime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiv1.CreateCertificateResponse{
|
||||||
|
Certificate: cert,
|
||||||
|
CertificateChain: chain,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCertificateAuthority returns the root certificate of the certificate
|
||||||
|
// authority using the configured fingerprint.
|
||||||
|
func (v *VaultCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityRequest) (*apiv1.GetCertificateAuthorityResponse, error) {
|
||||||
|
secret, err := v.client.Logical().Read(v.config.PKI + "/cert/ca_chain")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading ca chain: %w", err)
|
||||||
|
}
|
||||||
|
if secret == nil {
|
||||||
|
return nil, errors.New("error reading ca chain: response is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, ok := secret.Data["certificate"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("error unmarshaling vault response: certificate not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := getCertificateBundle(chain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cert.root == nil {
|
||||||
|
return nil, errors.New("error unmarshaling vault response: root certificate not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := sha256.Sum256(cert.root.Raw)
|
||||||
|
if !strings.EqualFold(v.fingerprint, strings.ToLower(hex.EncodeToString(sum[:]))) {
|
||||||
|
return nil, errors.New("error verifying vault root: fingerprint does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiv1.GetCertificateAuthorityResponse{
|
||||||
|
RootCertificate: cert.root,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewCertificate will always return a non-implemented error as renewals
|
||||||
|
// are not supported yet.
|
||||||
|
func (v *VaultCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
|
||||||
|
return nil, apiv1.ErrNotImplemented{Message: "vaultCAS does not support renewals"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeCertificate revokes a certificate by serial number.
|
||||||
|
func (v *VaultCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
|
||||||
|
if req.SerialNumber == "" && req.Certificate == nil {
|
||||||
|
return nil, errors.New("revokeCertificate `serialNumber` or `certificate` are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var sn *big.Int
|
||||||
|
if req.SerialNumber != "" {
|
||||||
|
var ok bool
|
||||||
|
if sn, ok = new(big.Int).SetString(req.SerialNumber, 10); !ok {
|
||||||
|
return nil, fmt.Errorf("error parsing serialNumber: %v cannot be converted to big.Int", req.SerialNumber)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sn = req.Certificate.SerialNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
vaultReq := map[string]interface{}{
|
||||||
|
"serial_number": formatSerialNumber(sn),
|
||||||
|
}
|
||||||
|
_, err := v.client.Logical().Write(v.config.PKI+"/revoke/", vaultReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error revoking certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiv1.RevokeCertificateResponse{
|
||||||
|
Certificate: req.Certificate,
|
||||||
|
CertificateChain: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VaultCAS) createCertificate(cr *x509.CertificateRequest, lifetime time.Duration) (*x509.Certificate, []*x509.Certificate, error) {
|
||||||
|
var vaultPKIRole string
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case cr.PublicKeyAlgorithm == x509.RSA:
|
||||||
|
vaultPKIRole = v.config.PKIRoleRSA
|
||||||
|
case cr.PublicKeyAlgorithm == x509.ECDSA:
|
||||||
|
vaultPKIRole = v.config.PKIRoleEC
|
||||||
|
case cr.PublicKeyAlgorithm == x509.Ed25519:
|
||||||
|
vaultPKIRole = v.config.PKIRoleEd25519
|
||||||
|
default:
|
||||||
|
return nil, nil, fmt.Errorf("unsupported public key algorithm %v", cr.PublicKeyAlgorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
vaultReq := map[string]interface{}{
|
||||||
|
"csr": string(pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE REQUEST",
|
||||||
|
Bytes: cr.Raw,
|
||||||
|
})),
|
||||||
|
"format": "pem_bundle",
|
||||||
|
"ttl": lifetime.Seconds(),
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := v.client.Logical().Write(v.config.PKI+"/sign/"+vaultPKIRole, vaultReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error signing certificate: %w", err)
|
||||||
|
}
|
||||||
|
if secret == nil {
|
||||||
|
return nil, nil, errors.New("error signing certificate: response is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, ok := secret.Data["certificate"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, errors.New("error unmarshaling vault response: certificate not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := getCertificateBundle(chain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return certificate and certificate chain
|
||||||
|
return cert.leaf, cert.intermediates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOptions(config json.RawMessage) (*VaultOptions, error) {
|
||||||
|
var vc *VaultOptions
|
||||||
|
|
||||||
|
err := json.Unmarshal(config, &vc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding vaultCAS config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vc.PKI == "" {
|
||||||
|
vc.PKI = "pki" // use default pki vault name
|
||||||
|
}
|
||||||
|
|
||||||
|
if vc.PKIRoleDefault == "" {
|
||||||
|
vc.PKIRoleDefault = "default" // use default pki role name
|
||||||
|
}
|
||||||
|
|
||||||
|
if vc.PKIRoleRSA == "" {
|
||||||
|
vc.PKIRoleRSA = vc.PKIRoleDefault
|
||||||
|
}
|
||||||
|
if vc.PKIRoleEC == "" {
|
||||||
|
vc.PKIRoleEC = vc.PKIRoleDefault
|
||||||
|
}
|
||||||
|
if vc.PKIRoleEd25519 == "" {
|
||||||
|
vc.PKIRoleEd25519 = vc.PKIRoleDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
if vc.RoleID == "" {
|
||||||
|
return nil, errors.New("vaultCAS config options must define `roleID`")
|
||||||
|
}
|
||||||
|
|
||||||
|
if vc.SecretID.FromEnv == "" && vc.SecretID.FromFile == "" && vc.SecretID.FromString == "" {
|
||||||
|
return nil, errors.New("vaultCAS config options must define `secretID` object with one of `FromEnv`, `FromFile` or `FromString`")
|
||||||
|
}
|
||||||
|
|
||||||
|
if vc.PKI == "" {
|
||||||
|
vc.PKI = "pki" // use default pki vault name
|
||||||
|
}
|
||||||
|
|
||||||
|
if vc.AppRole == "" {
|
||||||
|
vc.AppRole = "auth/approle"
|
||||||
|
}
|
||||||
|
|
||||||
|
return vc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCertificates(pemCert string) []*x509.Certificate {
|
||||||
|
var certs []*x509.Certificate
|
||||||
|
rest := []byte(pemCert)
|
||||||
|
var block *pem.Block
|
||||||
|
for {
|
||||||
|
block, rest = pem.Decode(rest)
|
||||||
|
if block == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
certs = append(certs, cert)
|
||||||
|
}
|
||||||
|
return certs
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCertificateBundle(chain string) (*certBundle, error) {
|
||||||
|
var root *x509.Certificate
|
||||||
|
var leaf *x509.Certificate
|
||||||
|
var intermediates []*x509.Certificate
|
||||||
|
for _, cert := range parseCertificates(chain) {
|
||||||
|
switch {
|
||||||
|
case isRoot(cert):
|
||||||
|
root = cert
|
||||||
|
case cert.BasicConstraintsValid && cert.IsCA:
|
||||||
|
intermediates = append(intermediates, cert)
|
||||||
|
default:
|
||||||
|
leaf = cert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate := &certBundle{
|
||||||
|
root: root,
|
||||||
|
leaf: leaf,
|
||||||
|
intermediates: intermediates,
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRoot returns true if the given certificate is a root certificate.
|
||||||
|
func isRoot(cert *x509.Certificate) bool {
|
||||||
|
if cert.BasicConstraintsValid && cert.IsCA {
|
||||||
|
return cert.CheckSignatureFrom(cert) == nil
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatSerialNumber formats a serial number to a dash-separated hexadecimal
|
||||||
|
// string.
|
||||||
|
func formatSerialNumber(sn *big.Int) string {
|
||||||
|
var ret bytes.Buffer
|
||||||
|
for _, b := range sn.Bytes() {
|
||||||
|
if ret.Len() > 0 {
|
||||||
|
ret.WriteString("-")
|
||||||
|
}
|
||||||
|
ret.WriteString(hex.EncodeToString([]byte{b}))
|
||||||
|
}
|
||||||
|
return ret.String()
|
||||||
|
}
|
@ -0,0 +1,676 @@
|
|||||||
|
package vaultcas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
vault "github.com/hashicorp/vault/api"
|
||||||
|
auth "github.com/hashicorp/vault/api/auth/approle"
|
||||||
|
"github.com/smallstep/certificates/cas/apiv1"
|
||||||
|
"go.step.sm/crypto/pemutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testCertificateSigned = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIB/DCCAaKgAwIBAgIQHHFuGMz0cClfde5kqP5prTAKBggqhkjOPQQDAjAqMSgw
|
||||||
|
JgYDVQQDEx9Hb29nbGUgQ0FTIFRlc3QgSW50ZXJtZWRpYXRlIENBMB4XDTIwMDkx
|
||||||
|
NTAwMDQ0M1oXDTMwMDkxMzAwMDQ0MFowHTEbMBkGA1UEAxMSdGVzdC5zbWFsbHN0
|
||||||
|
ZXAuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMqNCiXMvbn74LsHzRv+8
|
||||||
|
17m9vEzH6RHrg3m82e0uEc36+fZWV/zJ9SKuONmnl5VP79LsjL5SVH0RDj73U2XO
|
||||||
|
DKOBtjCBszAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
|
||||||
|
AQUFBwMCMB0GA1UdDgQWBBRTA2cTs7PCNjnps/+T0dS8diqv0DAfBgNVHSMEGDAW
|
||||||
|
gBRIOVqyLDSlErJLuWWEvRm5UU1r1TBCBgwrBgEEAYKkZMYoQAIEMjAwEwhjbG91
|
||||||
|
ZGNhcxMkZDhkMThhNjgtNTI5Ni00YWYzLWFlNGItMmY4NzdkYTNmYmQ5MAoGCCqG
|
||||||
|
SM49BAMCA0gAMEUCIGxl+pqJ50WYWUqK2l4V1FHoXSi0Nht5kwTxFxnWZu1xAiEA
|
||||||
|
zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
testCertificateCsrEc = `-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIHoMIGPAgEAMA0xCzAJBgNVBAMTAkVDMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
|
||||||
|
QgAEUVVVZGD6eUrB20T/qrjKZoYzseQ18AIm9jtUNpQn5hIClpdk2zKy5bja3iUa
|
||||||
|
nmqRKCIz/B/MU55zuNDeckqqX6AgMB4GCSqGSIb3DQEJDjERMA8wDQYDVR0RBAYw
|
||||||
|
BIICRUMwCgYIKoZIzj0EAwIDSAAwRQIhAJxpWyH7cctbzcnK1JBWDAmc/G61bq9y
|
||||||
|
otHrQDfYvS8bAiBVGQz2cfO2SqhvkkQbOqWUFjk1wHzISvlTjyc3IJ7FLw==
|
||||||
|
-----END CERTIFICATE REQUEST-----`
|
||||||
|
testCertificateCsrRsa = `-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIICdDCCAVwCAQAwDjEMMAoGA1UEAxMDUlNBMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||||
|
AQ8AMIIBCgKCAQEAxe5XLSZrTCzzH0FJCXvZwghAY5XztzjseSRcm0jL8Q7nvNWi
|
||||||
|
Vpu1n7EmfVU9b8sbvtVYqMQV+hMdj2C/NIw4Yal4Wg+BgunYOrRqfY7oDm4csG0R
|
||||||
|
g5v0h2yQw14kqVrftNyojX0Nv/CPboCGl64PA9zsEXQTB3Y1AUWrUGPiBWNACYIH
|
||||||
|
mjv70Ay9JKBBAqov38I7nka/RgYAl5DCHzU2vvODriBYFWagnzycA4Ni5EKTz93W
|
||||||
|
SPdDEhkWi3ugUqal3SvgHl8re+8d7ghLn85Y3TFuyU2nSMDPHaymsiNFw1mRwOw3
|
||||||
|
lAseidHJkPQs7q6FiYXaeqetf1j/gw0n23ZogwIDAQABoCEwHwYJKoZIhvcNAQkO
|
||||||
|
MRIwEDAOBgNVHREEBzAFggNSU0EwDQYJKoZIhvcNAQELBQADggEBALnO5vcDkgGO
|
||||||
|
GQoSINa2NmNFxAtYQGYHok5KXYX+S+etmOmDrmrhsl/pSjN3GPCPlThFlbLStB70
|
||||||
|
oJw67nEjGf0hPEBVlm+qFUsYQ1KGRZFAWDSMQ//pU225XFDCmlzHfV7gZjSkP9GN
|
||||||
|
Gc5VECOzx6hAFR+IEL/l/1GG5HHkPPrr/8OvuIfm2V5ofYmhsXMVVYH52qPofMAV
|
||||||
|
B8UdNnZK3nyLdUqVd+PYUUJmN4bJ8YfxofKKgbLkhvkKp4OZ9vkwUi2+61NdHTf2
|
||||||
|
wIauOyxEoTlJpU6oA/sxu/2Ht2DP+8y6mognLBuKklE/VH3/2iqQWyg1NV5hyg3b
|
||||||
|
loVSdLsIh5Y=
|
||||||
|
-----END CERTIFICATE REQUEST-----`
|
||||||
|
testCertificateCsrEd25519 = `-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIGuMGICAQAwDjEMMAoGA1UEAxMDT0tQMCowBQYDK2VwAyEAopc6daK4zYR6BDAM
|
||||||
|
pV/v53oR/ewbtrkHZQkN/amFMLagITAfBgkqhkiG9w0BCQ4xEjAQMA4GA1UdEQQH
|
||||||
|
MAWCA09LUDAFBgMrZXADQQDJi47MAgl/WKAz+V/kDu1k/zbKk1nrHHAUonbofHUW
|
||||||
|
M6ihSD43+awq3BPeyPbToeH5orSH9l3MuTfbxPb5BVEH
|
||||||
|
-----END CERTIFICATE REQUEST-----`
|
||||||
|
testRootCertificate = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBeDCCAR+gAwIBAgIQcXWWjtSZ/PAyH8D1Ou4L9jAKBggqhkjOPQQDAjAbMRkw
|
||||||
|
FwYDVQQDExBDbG91ZENBUyBSb290IENBMB4XDTIwMTAyNzIyNTM1NFoXDTMwMTAy
|
||||||
|
NzIyNTM1NFowGzEZMBcGA1UEAxMQQ2xvdWRDQVMgUm9vdCBDQTBZMBMGByqGSM49
|
||||||
|
AgEGCCqGSM49AwEHA0IABIySHA4b78Yu4LuGhZIlv/PhNwXz4ZoV1OUZQ0LrK3vj
|
||||||
|
B13O12DLZC5uj1z3kxdQzXUttSbtRv49clMpBiTpsZKjRTBDMA4GA1UdDwEB/wQE
|
||||||
|
AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSZ+t9RMHbFTl5BatM3
|
||||||
|
5bJlHPOu3DAKBggqhkjOPQQDAgNHADBEAiASah6gg0tVM3WI0meCQ4SEKk7Mjhbv
|
||||||
|
+SmhuZHWV1QlXQIgRXNyWcpVUrAoG6Uy1KQg07LDpF5dFeK9InrDxSJAkVo=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
testRootFingerprint = `62e816cbac5c501b7705e18415503852798dfbcd67062f06bcb4af67c290e3c8`
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate {
|
||||||
|
t.Helper()
|
||||||
|
crt := parseCertificates(pemCert)[0]
|
||||||
|
return crt
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParseCertificateRequest(t *testing.T, pemData string) *x509.CertificateRequest {
|
||||||
|
t.Helper()
|
||||||
|
csr, err := pemutil.ParseCertificateRequest([]byte(pemData))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return csr
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCAHelper(t *testing.T) (*url.URL, *vault.Client) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
writeJSON := func(w http.ResponseWriter, v interface{}) {
|
||||||
|
_ = json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.RequestURI == "/v1/auth/auth/approle/login":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, `{
|
||||||
|
"auth": {
|
||||||
|
"client_token": "98a4c7ab-b1fe-361b-ba0b-e307aacfd587"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
case r.RequestURI == "/v1/pki/sign/ec":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
cert := map[string]interface{}{"data": map[string]interface{}{"certificate": testCertificateSigned + "\n" + testRootCertificate}}
|
||||||
|
writeJSON(w, cert)
|
||||||
|
return
|
||||||
|
case r.RequestURI == "/v1/pki/sign/rsa":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
cert := map[string]interface{}{"data": map[string]interface{}{"certificate": testCertificateSigned + "\n" + testRootCertificate}}
|
||||||
|
writeJSON(w, cert)
|
||||||
|
return
|
||||||
|
case r.RequestURI == "/v1/pki/sign/ed25519":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
cert := map[string]interface{}{"data": map[string]interface{}{"certificate": testCertificateSigned + "\n" + testRootCertificate}}
|
||||||
|
writeJSON(w, cert)
|
||||||
|
return
|
||||||
|
case r.RequestURI == "/v1/pki/cert/ca_chain":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
cert := map[string]interface{}{"data": map[string]interface{}{"certificate": testCertificateSigned + "\n" + testRootCertificate}}
|
||||||
|
writeJSON(w, cert)
|
||||||
|
return
|
||||||
|
case r.RequestURI == "/v1/pki/revoke":
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.ReadFrom(r.Body)
|
||||||
|
m := make(map[string]string)
|
||||||
|
json.Unmarshal(buf.Bytes(), &m)
|
||||||
|
switch {
|
||||||
|
case m["serial_number"] == "1c-71-6e-18-cc-f4-70-29-5f-75-ee-64-a8-fe-69-ad":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
case m["serial_number"] == "01-e2-40":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
// both
|
||||||
|
case m["serial_number"] == "01-34-3e":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintf(w, `{"error":"not found"}`)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
t.Cleanup(func() {
|
||||||
|
srv.Close()
|
||||||
|
})
|
||||||
|
u, err := url.Parse(srv.URL)
|
||||||
|
if err != nil {
|
||||||
|
srv.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := vault.DefaultConfig()
|
||||||
|
config.Address = srv.URL
|
||||||
|
|
||||||
|
client, err := vault.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
srv.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, client
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew_register(t *testing.T) {
|
||||||
|
caURL, _ := testCAHelper(t)
|
||||||
|
|
||||||
|
fn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.VaultCAS)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("apiv1.Register() ok = %v, want true", ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := fn(context.Background(), apiv1.Options{
|
||||||
|
CertificateAuthority: caURL.String(),
|
||||||
|
CertificateAuthorityFingerprint: testRootFingerprint,
|
||||||
|
Config: json.RawMessage(`{
|
||||||
|
"PKI": "pki",
|
||||||
|
"PKIRoleDefault": "pki-role",
|
||||||
|
"RoleID": "roleID",
|
||||||
|
"SecretID": {"FromString": "secretID"},
|
||||||
|
"IsWrappingToken": false
|
||||||
|
}`),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("New() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVaultCAS_CreateCertificate(t *testing.T) {
|
||||||
|
_, client := testCAHelper(t)
|
||||||
|
|
||||||
|
options := VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "role",
|
||||||
|
PKIRoleRSA: "rsa",
|
||||||
|
PKIRoleEC: "ec",
|
||||||
|
PKIRoleEd25519: "ed25519",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromString: "secretID"},
|
||||||
|
AppRole: "approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
client *vault.Client
|
||||||
|
options VaultOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
req *apiv1.CreateCertificateRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want *apiv1.CreateCertificateResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok ec", fields{client, options}, args{&apiv1.CreateCertificateRequest{
|
||||||
|
CSR: mustParseCertificateRequest(t, testCertificateCsrEc),
|
||||||
|
Lifetime: time.Hour,
|
||||||
|
}}, &apiv1.CreateCertificateResponse{
|
||||||
|
Certificate: mustParseCertificate(t, testCertificateSigned),
|
||||||
|
CertificateChain: nil,
|
||||||
|
}, false},
|
||||||
|
{"ok rsa", fields{client, options}, args{&apiv1.CreateCertificateRequest{
|
||||||
|
CSR: mustParseCertificateRequest(t, testCertificateCsrRsa),
|
||||||
|
Lifetime: time.Hour,
|
||||||
|
}}, &apiv1.CreateCertificateResponse{
|
||||||
|
Certificate: mustParseCertificate(t, testCertificateSigned),
|
||||||
|
CertificateChain: nil,
|
||||||
|
}, false},
|
||||||
|
{"ok ed25519", fields{client, options}, args{&apiv1.CreateCertificateRequest{
|
||||||
|
CSR: mustParseCertificateRequest(t, testCertificateCsrEd25519),
|
||||||
|
Lifetime: time.Hour,
|
||||||
|
}}, &apiv1.CreateCertificateResponse{
|
||||||
|
Certificate: mustParseCertificate(t, testCertificateSigned),
|
||||||
|
CertificateChain: nil,
|
||||||
|
}, false},
|
||||||
|
{"fail CSR", fields{client, options}, args{&apiv1.CreateCertificateRequest{
|
||||||
|
CSR: nil,
|
||||||
|
Lifetime: time.Hour,
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail lifetime", fields{client, options}, args{&apiv1.CreateCertificateRequest{
|
||||||
|
CSR: mustParseCertificateRequest(t, testCertificateCsrEc),
|
||||||
|
Lifetime: 0,
|
||||||
|
}}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &VaultCAS{
|
||||||
|
client: tt.fields.client,
|
||||||
|
config: tt.fields.options,
|
||||||
|
}
|
||||||
|
got, err := c.CreateCertificate(tt.args.req)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("VaultCAS.CreateCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("VaultCAS.CreateCertificate() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVaultCAS_GetCertificateAuthority(t *testing.T) {
|
||||||
|
caURL, client := testCAHelper(t)
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
client *vault.Client
|
||||||
|
options VaultOptions
|
||||||
|
fingerprint string
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
req *apiv1.GetCertificateAuthorityRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
options := VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCert := parseCertificates(testRootCertificate)[0]
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want *apiv1.GetCertificateAuthorityResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", fields{client, options, testRootFingerprint}, args{&apiv1.GetCertificateAuthorityRequest{
|
||||||
|
Name: caURL.String(),
|
||||||
|
}}, &apiv1.GetCertificateAuthorityResponse{
|
||||||
|
RootCertificate: rootCert,
|
||||||
|
}, false},
|
||||||
|
{"fail fingerprint", fields{client, options, "fail"}, args{&apiv1.GetCertificateAuthorityRequest{
|
||||||
|
Name: caURL.String(),
|
||||||
|
}}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &VaultCAS{
|
||||||
|
client: tt.fields.client,
|
||||||
|
fingerprint: tt.fields.fingerprint,
|
||||||
|
config: tt.fields.options,
|
||||||
|
}
|
||||||
|
got, err := s.GetCertificateAuthority(tt.args.req)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("VaultCAS.GetCertificateAuthority() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("VaultCAS.GetCertificateAuthority() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVaultCAS_RevokeCertificate(t *testing.T) {
|
||||||
|
_, client := testCAHelper(t)
|
||||||
|
|
||||||
|
options := VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "role",
|
||||||
|
PKIRoleRSA: "rsa",
|
||||||
|
PKIRoleEC: "ec",
|
||||||
|
PKIRoleEd25519: "ed25519",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromString: "secretID"},
|
||||||
|
AppRole: "approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
client *vault.Client
|
||||||
|
options VaultOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
req *apiv1.RevokeCertificateRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
testCrt := parseCertificates(testCertificateSigned)[0]
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want *apiv1.RevokeCertificateResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok serial number", fields{client, options}, args{&apiv1.RevokeCertificateRequest{
|
||||||
|
SerialNumber: "123456",
|
||||||
|
Certificate: nil,
|
||||||
|
}}, &apiv1.RevokeCertificateResponse{}, false},
|
||||||
|
{"ok certificate", fields{client, options}, args{&apiv1.RevokeCertificateRequest{
|
||||||
|
SerialNumber: "",
|
||||||
|
Certificate: testCrt,
|
||||||
|
}}, &apiv1.RevokeCertificateResponse{
|
||||||
|
Certificate: testCrt,
|
||||||
|
}, false},
|
||||||
|
{"ok both", fields{client, options}, args{&apiv1.RevokeCertificateRequest{
|
||||||
|
SerialNumber: "78910",
|
||||||
|
Certificate: testCrt,
|
||||||
|
}}, &apiv1.RevokeCertificateResponse{
|
||||||
|
Certificate: testCrt,
|
||||||
|
}, false},
|
||||||
|
{"fail serial string", fields{client, options}, args{&apiv1.RevokeCertificateRequest{
|
||||||
|
SerialNumber: "fail",
|
||||||
|
Certificate: nil,
|
||||||
|
}}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &VaultCAS{
|
||||||
|
client: tt.fields.client,
|
||||||
|
config: tt.fields.options,
|
||||||
|
}
|
||||||
|
got, err := s.RevokeCertificate(tt.args.req)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("VaultCAS.RevokeCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("VaultCAS.RevokeCertificate() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVaultCAS_RenewCertificate(t *testing.T) {
|
||||||
|
_, client := testCAHelper(t)
|
||||||
|
|
||||||
|
options := VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "role",
|
||||||
|
PKIRoleRSA: "rsa",
|
||||||
|
PKIRoleEC: "ec",
|
||||||
|
PKIRoleEd25519: "ed25519",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromString: "secretID"},
|
||||||
|
AppRole: "approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
client *vault.Client
|
||||||
|
options VaultOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
req *apiv1.RenewCertificateRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want *apiv1.RenewCertificateResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"not implemented", fields{client, options}, args{&apiv1.RenewCertificateRequest{
|
||||||
|
CSR: mustParseCertificateRequest(t, testCertificateCsrEc),
|
||||||
|
Lifetime: time.Hour,
|
||||||
|
}}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &VaultCAS{
|
||||||
|
client: tt.fields.client,
|
||||||
|
config: tt.fields.options,
|
||||||
|
}
|
||||||
|
got, err := s.RenewCertificate(tt.args.req)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("VaultCAS.RenewCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("VaultCAS.RenewCertificate() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVaultCAS_loadOptions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
raw string
|
||||||
|
want *VaultOptions
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ok mandatory with SecretID FromString",
|
||||||
|
`{"RoleID": "roleID", "SecretID": {"FromString": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "default",
|
||||||
|
PKIRoleRSA: "default",
|
||||||
|
PKIRoleEC: "default",
|
||||||
|
PKIRoleEd25519: "default",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromString: "secretID"},
|
||||||
|
AppRole: "auth/approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok mandatory with SecretID FromFile",
|
||||||
|
`{"RoleID": "roleID", "SecretID": {"FromFile": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "default",
|
||||||
|
PKIRoleRSA: "default",
|
||||||
|
PKIRoleEC: "default",
|
||||||
|
PKIRoleEd25519: "default",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromFile: "secretID"},
|
||||||
|
AppRole: "auth/approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok mandatory with SecretID FromEnv",
|
||||||
|
`{"RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "default",
|
||||||
|
PKIRoleRSA: "default",
|
||||||
|
PKIRoleEC: "default",
|
||||||
|
PKIRoleEd25519: "default",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromEnv: "secretID"},
|
||||||
|
AppRole: "auth/approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok mandatory PKIRole PKIRoleEd25519",
|
||||||
|
`{"PKIRoleDefault": "role", "PKIRoleEd25519": "ed25519" , "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "role",
|
||||||
|
PKIRoleRSA: "role",
|
||||||
|
PKIRoleEC: "role",
|
||||||
|
PKIRoleEd25519: "ed25519",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromEnv: "secretID"},
|
||||||
|
AppRole: "auth/approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok mandatory PKIRole PKIRoleEC",
|
||||||
|
`{"PKIRoleDefault": "role", "PKIRoleEC": "ec" , "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "role",
|
||||||
|
PKIRoleRSA: "role",
|
||||||
|
PKIRoleEC: "ec",
|
||||||
|
PKIRoleEd25519: "role",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromEnv: "secretID"},
|
||||||
|
AppRole: "auth/approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok mandatory PKIRole PKIRoleRSA",
|
||||||
|
`{"PKIRoleDefault": "role", "PKIRoleRSA": "rsa" , "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "role",
|
||||||
|
PKIRoleRSA: "rsa",
|
||||||
|
PKIRoleEC: "role",
|
||||||
|
PKIRoleEd25519: "role",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromEnv: "secretID"},
|
||||||
|
AppRole: "auth/approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok mandatory PKIRoleRSA PKIRoleEC PKIRoleEd25519",
|
||||||
|
`{"PKIRoleRSA": "rsa", "PKIRoleEC": "ec", "PKIRoleEd25519": "ed25519", "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "default",
|
||||||
|
PKIRoleRSA: "rsa",
|
||||||
|
PKIRoleEC: "ec",
|
||||||
|
PKIRoleEd25519: "ed25519",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromEnv: "secretID"},
|
||||||
|
AppRole: "auth/approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok mandatory PKIRoleRSA PKIRoleEC PKIRoleEd25519 with useless PKIRoleDefault",
|
||||||
|
`{"PKIRoleDefault": "role", "PKIRoleRSA": "rsa", "PKIRoleEC": "ec", "PKIRoleEd25519": "ed25519", "RoleID": "roleID", "SecretID": {"FromEnv": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "role",
|
||||||
|
PKIRoleRSA: "rsa",
|
||||||
|
PKIRoleEC: "ec",
|
||||||
|
PKIRoleEd25519: "ed25519",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromEnv: "secretID"},
|
||||||
|
AppRole: "auth/approle",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok mandatory with AppRole",
|
||||||
|
`{"AppRole": "test", "RoleID": "roleID", "SecretID": {"FromString": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "default",
|
||||||
|
PKIRoleRSA: "default",
|
||||||
|
PKIRoleEC: "default",
|
||||||
|
PKIRoleEd25519: "default",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromString: "secretID"},
|
||||||
|
AppRole: "test",
|
||||||
|
IsWrappingToken: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ok mandatory with IsWrappingToken",
|
||||||
|
`{"IsWrappingToken": true, "RoleID": "roleID", "SecretID": {"FromString": "secretID"}}`,
|
||||||
|
&VaultOptions{
|
||||||
|
PKI: "pki",
|
||||||
|
PKIRoleDefault: "default",
|
||||||
|
PKIRoleRSA: "default",
|
||||||
|
PKIRoleEC: "default",
|
||||||
|
PKIRoleEd25519: "default",
|
||||||
|
RoleID: "roleID",
|
||||||
|
SecretID: auth.SecretID{FromString: "secretID"},
|
||||||
|
AppRole: "auth/approle",
|
||||||
|
IsWrappingToken: true,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail with SecretID FromFail",
|
||||||
|
`{"RoleID": "roleID", "SecretID": {"FromFail": "secretID"}}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail with SecretID empty FromEnv",
|
||||||
|
`{"RoleID": "roleID", "SecretID": {"FromEnv": ""}}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail with SecretID empty FromFile",
|
||||||
|
`{"RoleID": "roleID", "SecretID": {"FromFile": ""}}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail with SecretID empty FromString",
|
||||||
|
`{"RoleID": "roleID", "SecretID": {"FromString": ""}}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail mandatory with SecretID FromFail",
|
||||||
|
`{"RoleID": "roleID", "SecretID": {"FromFail": "secretID"}}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail missing RoleID",
|
||||||
|
`{"SecretID": {"FromString": "secretID"}}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := loadOptions(json.RawMessage(tt.raw))
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("VaultCAS.loadOptions() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("VaultCAS.loadOptions() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue