Files
vault/builtin/credential/cert/backend_test.go
hashicorp-copywrite[bot] 0b12cdcfd1 [COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

2339 lines
69 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cert
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"math/big"
mathrand "math/rand"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/go-test/deep"
cleanhttp "github.com/hashicorp/go-cleanhttp"
rootcerts "github.com/hashicorp/go-rootcerts"
"github.com/hashicorp/go-sockaddr"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/logical/pki"
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/tokenutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/mapstructure"
"golang.org/x/net/http2"
)
const (
serverCertPath = "test-fixtures/cacert.pem"
serverKeyPath = "test-fixtures/cakey.pem"
serverCAPath = serverCertPath
testRootCACertPath1 = "test-fixtures/testcacert1.pem"
testRootCAKeyPath1 = "test-fixtures/testcakey1.pem"
testCertPath1 = "test-fixtures/testissuedcert4.pem"
testKeyPath1 = "test-fixtures/testissuedkey4.pem"
testIssuedCertCRL = "test-fixtures/issuedcertcrl"
testRootCACertPath2 = "test-fixtures/testcacert2.pem"
testRootCAKeyPath2 = "test-fixtures/testcakey2.pem"
testRootCertCRL = "test-fixtures/cacert2crl"
)
func generateTestCertAndConnState(t *testing.T, template *x509.Certificate) (string, tls.ConnectionState, error) {
t.Helper()
tempDir, err := ioutil.TempDir("", "vault-cert-auth-test-")
if err != nil {
t.Fatal(err)
}
t.Logf("test %s, temp dir %s", t.Name(), tempDir)
caCertTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "localhost",
},
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
BasicConstraintsValid: true,
IsCA: true,
}
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
caBytes, err := x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, caKey.Public(), caKey)
if err != nil {
t.Fatal(err)
}
caCert, err := x509.ParseCertificate(caBytes)
if err != nil {
t.Fatal(err)
}
caCertPEMBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
}
err = ioutil.WriteFile(filepath.Join(tempDir, "ca_cert.pem"), pem.EncodeToMemory(caCertPEMBlock), 0o755)
if err != nil {
t.Fatal(err)
}
marshaledCAKey, err := x509.MarshalECPrivateKey(caKey)
if err != nil {
t.Fatal(err)
}
caKeyPEMBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: marshaledCAKey,
}
err = ioutil.WriteFile(filepath.Join(tempDir, "ca_key.pem"), pem.EncodeToMemory(caKeyPEMBlock), 0o755)
if err != nil {
t.Fatal(err)
}
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, key.Public(), caKey)
if err != nil {
t.Fatal(err)
}
certPEMBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
}
err = ioutil.WriteFile(filepath.Join(tempDir, "cert.pem"), pem.EncodeToMemory(certPEMBlock), 0o755)
if err != nil {
t.Fatal(err)
}
marshaledKey, err := x509.MarshalECPrivateKey(key)
if err != nil {
t.Fatal(err)
}
keyPEMBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: marshaledKey,
}
err = ioutil.WriteFile(filepath.Join(tempDir, "key.pem"), pem.EncodeToMemory(keyPEMBlock), 0o755)
if err != nil {
t.Fatal(err)
}
connInfo, err := testConnState(filepath.Join(tempDir, "cert.pem"), filepath.Join(tempDir, "key.pem"), filepath.Join(tempDir, "ca_cert.pem"))
return tempDir, connInfo, err
}
// Unlike testConnState, this method does not use the same 'tls.Config' objects for
// both dialing and listening. Instead, it runs the server without specifying its CA.
// But the client, presents the CA cert of the server to trust the server.
// The client can present a cert and key which is completely independent of server's CA.
// The connection state returned will contain the certificate presented by the client.
func connectionState(serverCAPath, serverCertPath, serverKeyPath, clientCertPath, clientKeyPath string) (tls.ConnectionState, error) {
serverKeyPair, err := tls.LoadX509KeyPair(serverCertPath, serverKeyPath)
if err != nil {
return tls.ConnectionState{}, err
}
// Prepare the listener configuration with server's key pair
listenConf := &tls.Config{
Certificates: []tls.Certificate{serverKeyPair},
ClientAuth: tls.RequestClientCert,
}
clientKeyPair, err := tls.LoadX509KeyPair(clientCertPath, clientKeyPath)
if err != nil {
return tls.ConnectionState{}, err
}
// Load the CA cert required by the client to authenticate the server.
rootConfig := &rootcerts.Config{
CAFile: serverCAPath,
}
serverCAs, err := rootcerts.LoadCACerts(rootConfig)
if err != nil {
return tls.ConnectionState{}, err
}
// Prepare the dial configuration that the client uses to establish the connection.
dialConf := &tls.Config{
Certificates: []tls.Certificate{clientKeyPair},
RootCAs: serverCAs,
}
// Start the server.
list, err := tls.Listen("tcp", "127.0.0.1:0", listenConf)
if err != nil {
return tls.ConnectionState{}, err
}
defer list.Close()
// Accept connections.
serverErrors := make(chan error, 1)
connState := make(chan tls.ConnectionState)
go func() {
defer close(connState)
serverConn, err := list.Accept()
if err != nil {
serverErrors <- err
close(serverErrors)
return
}
defer serverConn.Close()
// Read the ping
buf := make([]byte, 4)
_, err = serverConn.Read(buf)
if (err != nil) && (err != io.EOF) {
serverErrors <- err
close(serverErrors)
return
}
close(serverErrors)
connState <- serverConn.(*tls.Conn).ConnectionState()
}()
// Establish a connection from the client side and write a few bytes.
clientErrors := make(chan error, 1)
go func() {
addr := list.Addr().String()
conn, err := tls.Dial("tcp", addr, dialConf)
if err != nil {
clientErrors <- err
close(clientErrors)
return
}
defer conn.Close()
// Write ping
_, err = conn.Write([]byte("ping"))
if err != nil {
clientErrors <- err
}
close(clientErrors)
}()
for err = range clientErrors {
if err != nil {
return tls.ConnectionState{}, fmt.Errorf("error in client goroutine:%v", err)
}
}
for err = range serverErrors {
if err != nil {
return tls.ConnectionState{}, fmt.Errorf("error in server goroutine:%v", err)
}
}
// Grab the current state
return <-connState, nil
}
func TestBackend_PermittedDNSDomainsIntermediateCA(t *testing.T) {
// Enable PKI secret engine and Cert auth method
coreConfig := &vault.CoreConfig{
CredentialBackends: map[string]logical.Factory{
"cert": Factory,
},
LogicalBackends: map[string]logical.Factory{
"pki": pki.Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
cores := cluster.Cores
vault.TestWaitActive(t, cores[0].Core)
client := cores[0].Client
var err error
// Mount /pki as a root CA
err = client.Sys().Mount("pki", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "32h",
},
})
if err != nil {
t.Fatal(err)
}
// Set the cluster's certificate as the root CA in /pki
pemBundleRootCA := string(cluster.CACertPEM) + string(cluster.CAKeyPEM)
_, err = client.Logical().Write("pki/config/ca", map[string]interface{}{
"pem_bundle": pemBundleRootCA,
})
if err != nil {
t.Fatal(err)
}
// Mount /pki2 to operate as an intermediate CA
err = client.Sys().Mount("pki2", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "32h",
},
})
if err != nil {
t.Fatal(err)
}
// Create a CSR for the intermediate CA
secret, err := client.Logical().Write("pki2/intermediate/generate/internal", nil)
if err != nil {
t.Fatal(err)
}
intermediateCSR := secret.Data["csr"].(string)
// Sign the intermediate CSR using /pki
secret, err = client.Logical().Write("pki/root/sign-intermediate", map[string]interface{}{
"permitted_dns_domains": ".myvault.com",
"csr": intermediateCSR,
})
if err != nil {
t.Fatal(err)
}
intermediateCertPEM := secret.Data["certificate"].(string)
// Configure the intermediate cert as the CA in /pki2
_, err = client.Logical().Write("pki2/intermediate/set-signed", map[string]interface{}{
"certificate": intermediateCertPEM,
})
if err != nil {
t.Fatal(err)
}
// Create a role on the intermediate CA mount
_, err = client.Logical().Write("pki2/roles/myvault-dot-com", map[string]interface{}{
"allowed_domains": "myvault.com",
"allow_subdomains": "true",
"max_ttl": "5m",
})
if err != nil {
t.Fatal(err)
}
// Issue a leaf cert using the intermediate CA
secret, err = client.Logical().Write("pki2/issue/myvault-dot-com", map[string]interface{}{
"common_name": "cert.myvault.com",
"format": "pem",
"ip_sans": "127.0.0.1",
})
if err != nil {
t.Fatal(err)
}
leafCertPEM := secret.Data["certificate"].(string)
leafCertKeyPEM := secret.Data["private_key"].(string)
// Enable the cert auth method
err = client.Sys().EnableAuthWithOptions("cert", &api.EnableAuthOptions{
Type: "cert",
})
if err != nil {
t.Fatal(err)
}
// Set the intermediate CA cert as a trusted certificate in the backend
_, err = client.Logical().Write("auth/cert/certs/myvault-dot-com", map[string]interface{}{
"display_name": "myvault.com",
"policies": "default",
"certificate": intermediateCertPEM,
})
if err != nil {
t.Fatal(err)
}
// Create temporary files for CA cert, client cert and client cert key.
// This is used to configure TLS in the api client.
caCertFile, err := ioutil.TempFile("", "caCert")
if err != nil {
t.Fatal(err)
}
defer os.Remove(caCertFile.Name())
if _, err := caCertFile.Write([]byte(cluster.CACertPEM)); err != nil {
t.Fatal(err)
}
if err := caCertFile.Close(); err != nil {
t.Fatal(err)
}
leafCertFile, err := ioutil.TempFile("", "leafCert")
if err != nil {
t.Fatal(err)
}
defer os.Remove(leafCertFile.Name())
if _, err := leafCertFile.Write([]byte(leafCertPEM)); err != nil {
t.Fatal(err)
}
if err := leafCertFile.Close(); err != nil {
t.Fatal(err)
}
leafCertKeyFile, err := ioutil.TempFile("", "leafCertKey")
if err != nil {
t.Fatal(err)
}
defer os.Remove(leafCertKeyFile.Name())
if _, err := leafCertKeyFile.Write([]byte(leafCertKeyPEM)); err != nil {
t.Fatal(err)
}
if err := leafCertKeyFile.Close(); err != nil {
t.Fatal(err)
}
// This function is a copy-pasta from the NewTestCluster, with the
// modification to reconfigure the TLS on the api client with the leaf
// certificate generated above.
getAPIClient := func(port int, tlsConfig *tls.Config) *api.Client {
transport := cleanhttp.DefaultPooledTransport()
transport.TLSClientConfig = tlsConfig.Clone()
if err := http2.ConfigureTransport(transport); err != nil {
t.Fatal(err)
}
client := &http.Client{
Transport: transport,
CheckRedirect: func(*http.Request, []*http.Request) error {
// This can of course be overridden per-test by using its own client
return fmt.Errorf("redirects not allowed in these tests")
},
}
config := api.DefaultConfig()
if config.Error != nil {
t.Fatal(config.Error)
}
config.Address = fmt.Sprintf("https://127.0.0.1:%d", port)
config.HttpClient = client
// Set the above issued certificates as the client certificates
config.ConfigureTLS(&api.TLSConfig{
CACert: caCertFile.Name(),
ClientCert: leafCertFile.Name(),
ClientKey: leafCertKeyFile.Name(),
})
apiClient, err := api.NewClient(config)
if err != nil {
t.Fatal(err)
}
return apiClient
}
// Create a new api client with the desired TLS configuration
newClient := getAPIClient(cores[0].Listeners[0].Address.Port, cores[0].TLSConfig())
secret, err = newClient.Logical().Write("auth/cert/login", map[string]interface{}{
"name": "myvault-dot-com",
})
if err != nil {
t.Fatal(err)
}
if secret.Auth == nil || secret.Auth.ClientToken == "" {
t.Fatalf("expected a successful authentication")
}
// testing pathLoginRenew for cert auth
oldAccessor := secret.Auth.Accessor
newClient.SetToken(client.Token())
secret, err = newClient.Logical().Write("auth/token/renew-accessor", map[string]interface{}{
"accessor": secret.Auth.Accessor,
"increment": 3600,
})
if err != nil {
t.Fatal(err)
}
if secret.Auth == nil || secret.Auth.ClientToken != "" || secret.Auth.LeaseDuration != 3600 || secret.Auth.Accessor != oldAccessor {
t.Fatalf("unexpected accessor renewal")
}
}
func TestBackend_MetadataBasedACLPolicy(t *testing.T) {
// Start cluster with cert auth method enabled
coreConfig := &vault.CoreConfig{
CredentialBackends: map[string]logical.Factory{
"cert": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
cores := cluster.Cores
vault.TestWaitActive(t, cores[0].Core)
client := cores[0].Client
var err error
// Enable the cert auth method
err = client.Sys().EnableAuthWithOptions("cert", &api.EnableAuthOptions{
Type: "cert",
})
if err != nil {
t.Fatal(err)
}
// Enable metadata in aliases
_, err = client.Logical().Write("auth/cert/config", map[string]interface{}{
"enable_identity_alias_metadata": true,
})
if err != nil {
t.Fatal(err)
}
// Retrieve its accessor id
auths, err := client.Sys().ListAuth()
if err != nil {
t.Fatal(err)
}
var accessor string
for _, auth := range auths {
if auth.Type == "cert" {
accessor = auth.Accessor
}
}
if accessor == "" {
t.Fatal("failed to find cert auth accessor")
}
// Write ACL policy
err = client.Sys().PutPolicy("metadata-based", fmt.Sprintf(`
path "kv/cn/{{identity.entity.aliases.%s.metadata.common_name}}" {
capabilities = ["read"]
}
path "kv/ext/{{identity.entity.aliases.%s.metadata.2-1-1-1}}" {
capabilities = ["read"]
}
`, accessor, accessor))
if err != nil {
t.Fatalf("err: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
// Set the trusted certificate in the backend
_, err = client.Logical().Write("auth/cert/certs/test", map[string]interface{}{
"display_name": "test",
"policies": "metadata-based",
"certificate": string(ca),
"allowed_metadata_extensions": "2.1.1.1,1.2.3.45",
})
if err != nil {
t.Fatal(err)
}
// This function is a copy-paste from the NewTestCluster, with the
// modification to reconfigure the TLS on the api client with a
// specific client certificate.
getAPIClient := func(port int, tlsConfig *tls.Config) *api.Client {
transport := cleanhttp.DefaultPooledTransport()
transport.TLSClientConfig = tlsConfig.Clone()
if err := http2.ConfigureTransport(transport); err != nil {
t.Fatal(err)
}
client := &http.Client{
Transport: transport,
CheckRedirect: func(*http.Request, []*http.Request) error {
// This can of course be overridden per-test by using its own client
return fmt.Errorf("redirects not allowed in these tests")
},
}
config := api.DefaultConfig()
if config.Error != nil {
t.Fatal(config.Error)
}
config.Address = fmt.Sprintf("https://127.0.0.1:%d", port)
config.HttpClient = client
// Set the client certificates
config.ConfigureTLS(&api.TLSConfig{
CACertBytes: cluster.CACertPEM,
ClientCert: "test-fixtures/root/rootcawextcert.pem",
ClientKey: "test-fixtures/root/rootcawextkey.pem",
})
apiClient, err := api.NewClient(config)
if err != nil {
t.Fatal(err)
}
return apiClient
}
// Create a new api client with the desired TLS configuration
newClient := getAPIClient(cores[0].Listeners[0].Address.Port, cores[0].TLSConfig())
var secret *api.Secret
secret, err = newClient.Logical().Write("auth/cert/login", map[string]interface{}{
"name": "test",
})
if err != nil {
t.Fatal(err)
}
if secret.Auth == nil || secret.Auth.ClientToken == "" {
t.Fatalf("expected a successful authentication")
}
// Check paths guarded by ACL policy
newClient.SetToken(secret.Auth.ClientToken)
_, err = newClient.Logical().Read("kv/cn/example.com")
if err != nil {
t.Fatal(err)
}
_, err = newClient.Logical().Read("kv/cn/not.example.com")
if err == nil {
t.Fatal("expected access denied")
}
_, err = newClient.Logical().Read("kv/ext/A UTF8String Extension")
if err != nil {
t.Fatal(err)
}
_, err = newClient.Logical().Read("kv/ext/bar")
if err == nil {
t.Fatal("expected access denied")
}
}
func TestBackend_NonCAExpiry(t *testing.T) {
var resp *logical.Response
var err error
// Create a self-signed certificate and issue a leaf certificate using the
// CA cert
template := &x509.Certificate{
SerialNumber: big.NewInt(1234),
Subject: pkix.Name{
CommonName: "localhost",
Organization: []string{"hashicorp"},
OrganizationalUnit: []string{"vault"},
},
BasicConstraintsValid: true,
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(50 * time.Second),
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
}
// Set IP SAN
parsedIP := net.ParseIP("127.0.0.1")
if parsedIP == nil {
t.Fatalf("failed to create parsed IP")
}
template.IPAddresses = []net.IP{parsedIP}
// Private key for CA cert
caPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
// Marshalling to be able to create PEM file
caPrivateKeyBytes := x509.MarshalPKCS1PrivateKey(caPrivateKey)
caPublicKey := &caPrivateKey.PublicKey
template.IsCA = true
caCertBytes, err := x509.CreateCertificate(rand.Reader, template, template, caPublicKey, caPrivateKey)
if err != nil {
t.Fatal(err)
}
caCert, err := x509.ParseCertificate(caCertBytes)
if err != nil {
t.Fatal(err)
}
parsedCaBundle := &certutil.ParsedCertBundle{
Certificate: caCert,
CertificateBytes: caCertBytes,
PrivateKeyBytes: caPrivateKeyBytes,
PrivateKeyType: certutil.RSAPrivateKey,
}
caCertBundle, err := parsedCaBundle.ToCertBundle()
if err != nil {
t.Fatal(err)
}
caCertFile, err := ioutil.TempFile("", "caCert")
if err != nil {
t.Fatal(err)
}
defer os.Remove(caCertFile.Name())
if _, err := caCertFile.Write([]byte(caCertBundle.Certificate)); err != nil {
t.Fatal(err)
}
if err := caCertFile.Close(); err != nil {
t.Fatal(err)
}
caKeyFile, err := ioutil.TempFile("", "caKey")
if err != nil {
t.Fatal(err)
}
defer os.Remove(caKeyFile.Name())
if _, err := caKeyFile.Write([]byte(caCertBundle.PrivateKey)); err != nil {
t.Fatal(err)
}
if err := caKeyFile.Close(); err != nil {
t.Fatal(err)
}
// Prepare template for non-CA cert
template.IsCA = false
template.SerialNumber = big.NewInt(5678)
template.KeyUsage = x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign)
issuedPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
issuedPrivateKeyBytes := x509.MarshalPKCS1PrivateKey(issuedPrivateKey)
issuedPublicKey := &issuedPrivateKey.PublicKey
// Keep a short certificate lifetime so logins can be tested both when
// cert is valid and when it gets expired
template.NotBefore = time.Now().Add(-2 * time.Second)
template.NotAfter = time.Now().Add(3 * time.Second)
issuedCertBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, issuedPublicKey, caPrivateKey)
if err != nil {
t.Fatal(err)
}
issuedCert, err := x509.ParseCertificate(issuedCertBytes)
if err != nil {
t.Fatal(err)
}
parsedIssuedBundle := &certutil.ParsedCertBundle{
Certificate: issuedCert,
CertificateBytes: issuedCertBytes,
PrivateKeyBytes: issuedPrivateKeyBytes,
PrivateKeyType: certutil.RSAPrivateKey,
}
issuedCertBundle, err := parsedIssuedBundle.ToCertBundle()
if err != nil {
t.Fatal(err)
}
issuedCertFile, err := ioutil.TempFile("", "issuedCert")
if err != nil {
t.Fatal(err)
}
defer os.Remove(issuedCertFile.Name())
if _, err := issuedCertFile.Write([]byte(issuedCertBundle.Certificate)); err != nil {
t.Fatal(err)
}
if err := issuedCertFile.Close(); err != nil {
t.Fatal(err)
}
issuedKeyFile, err := ioutil.TempFile("", "issuedKey")
if err != nil {
t.Fatal(err)
}
defer os.Remove(issuedKeyFile.Name())
if _, err := issuedKeyFile.Write([]byte(issuedCertBundle.PrivateKey)); err != nil {
t.Fatal(err)
}
if err := issuedKeyFile.Close(); err != nil {
t.Fatal(err)
}
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
// Register the Non-CA certificate of the client key pair
certData := map[string]interface{}{
"certificate": issuedCertBundle.Certificate,
"policies": "abc",
"display_name": "cert1",
"ttl": 10000,
}
certReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "certs/cert1",
Storage: storage,
Data: certData,
}
resp, err = b.HandleRequest(context.Background(), certReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Create connection state using the certificates generated
connState, err := connectionState(caCertFile.Name(), caCertFile.Name(), caKeyFile.Name(), issuedCertFile.Name(), issuedKeyFile.Name())
if err != nil {
t.Fatalf("error testing connection state:%v", err)
}
loginReq := &logical.Request{
Operation: logical.UpdateOperation,
Storage: storage,
Path: "login",
Connection: &logical.Connection{
ConnState: &connState,
},
}
// Login when the certificate is still valid. Login should succeed.
resp, err = b.HandleRequest(context.Background(), loginReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Wait until the certificate expires
time.Sleep(5 * time.Second)
// Login attempt after certificate expiry should fail
resp, err = b.HandleRequest(context.Background(), loginReq)
if err == nil {
t.Fatalf("expected error due to expired certificate")
}
}
func TestBackend_RegisteredNonCA_CRL(t *testing.T) {
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
nonCACert, err := ioutil.ReadFile(testCertPath1)
if err != nil {
t.Fatal(err)
}
// Register the Non-CA certificate of the client key pair
certData := map[string]interface{}{
"certificate": nonCACert,
"policies": "abc",
"display_name": "cert1",
"ttl": 10000,
}
certReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "certs/cert1",
Storage: storage,
Data: certData,
}
resp, err := b.HandleRequest(context.Background(), certReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Connection state is presenting the client Non-CA cert and its key.
// This is exactly what is registered at the backend.
connState, err := connectionState(serverCAPath, serverCertPath, serverKeyPath, testCertPath1, testKeyPath1)
if err != nil {
t.Fatalf("error testing connection state:%v", err)
}
loginReq := &logical.Request{
Operation: logical.UpdateOperation,
Storage: storage,
Path: "login",
Connection: &logical.Connection{
ConnState: &connState,
},
}
// Login should succeed.
resp, err = b.HandleRequest(context.Background(), loginReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Register a CRL containing the issued client certificate used above.
issuedCRL, err := ioutil.ReadFile(testIssuedCertCRL)
if err != nil {
t.Fatal(err)
}
crlData := map[string]interface{}{
"crl": issuedCRL,
}
crlReq := &logical.Request{
Operation: logical.UpdateOperation,
Storage: storage,
Path: "crls/issuedcrl",
Data: crlData,
}
resp, err = b.HandleRequest(context.Background(), crlReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Ensure the CRL shows up on a list.
listReq := &logical.Request{
Operation: logical.ListOperation,
Storage: storage,
Path: "crls",
Data: map[string]interface{}{},
}
resp, err = b.HandleRequest(context.Background(), listReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
if len(resp.Data) != 1 || len(resp.Data["keys"].([]string)) != 1 || resp.Data["keys"].([]string)[0] != "issuedcrl" {
t.Fatalf("bad listing: resp:%v", resp)
}
// Attempt login with the same connection state but with the CRL registered
resp, err = b.HandleRequest(context.Background(), loginReq)
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected failure due to revoked certificate")
}
}
func TestBackend_CRLs(t *testing.T) {
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
clientCA1, err := ioutil.ReadFile(testRootCACertPath1)
if err != nil {
t.Fatal(err)
}
// Register the CA certificate of the client key pair
certData := map[string]interface{}{
"certificate": clientCA1,
"policies": "abc",
"display_name": "cert1",
"ttl": 10000,
}
certReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "certs/cert1",
Storage: storage,
Data: certData,
}
resp, err := b.HandleRequest(context.Background(), certReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Connection state is presenting the client CA cert and its key.
// This is exactly what is registered at the backend.
connState, err := connectionState(serverCAPath, serverCertPath, serverKeyPath, testRootCACertPath1, testRootCAKeyPath1)
if err != nil {
t.Fatalf("error testing connection state:%v", err)
}
loginReq := &logical.Request{
Operation: logical.UpdateOperation,
Storage: storage,
Path: "login",
Connection: &logical.Connection{
ConnState: &connState,
},
}
resp, err = b.HandleRequest(context.Background(), loginReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Now, without changing the registered client CA cert, present from
// the client side, a cert issued using the registered CA.
connState, err = connectionState(serverCAPath, serverCertPath, serverKeyPath, testCertPath1, testKeyPath1)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
loginReq.Connection.ConnState = &connState
// Attempt login with the updated connection
resp, err = b.HandleRequest(context.Background(), loginReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Register a CRL containing the issued client certificate used above.
issuedCRL, err := ioutil.ReadFile(testIssuedCertCRL)
if err != nil {
t.Fatal(err)
}
crlData := map[string]interface{}{
"crl": issuedCRL,
}
crlReq := &logical.Request{
Operation: logical.UpdateOperation,
Storage: storage,
Path: "crls/issuedcrl",
Data: crlData,
}
resp, err = b.HandleRequest(context.Background(), crlReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Attempt login with the revoked certificate.
resp, err = b.HandleRequest(context.Background(), loginReq)
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected failure due to revoked certificate")
}
// Register a different client CA certificate.
clientCA2, err := ioutil.ReadFile(testRootCACertPath2)
if err != nil {
t.Fatal(err)
}
certData["certificate"] = clientCA2
resp, err = b.HandleRequest(context.Background(), certReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Test login using a different client CA cert pair.
connState, err = connectionState(serverCAPath, serverCertPath, serverKeyPath, testRootCACertPath2, testRootCAKeyPath2)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
loginReq.Connection.ConnState = &connState
// Attempt login with the updated connection
resp, err = b.HandleRequest(context.Background(), loginReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Register a CRL containing the root CA certificate used above.
rootCRL, err := ioutil.ReadFile(testRootCertCRL)
if err != nil {
t.Fatal(err)
}
crlData["crl"] = rootCRL
resp, err = b.HandleRequest(context.Background(), crlReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Attempt login with the same connection state but with the CRL registered
resp, err = b.HandleRequest(context.Background(), loginReq)
if err != nil {
t.Fatal(err)
}
if resp == nil || !resp.IsError() {
t.Fatalf("expected failure due to revoked certificate")
}
}
func testFactory(t *testing.T) logical.Backend {
storage := &logical.InmemStorage{}
b, err := Factory(context.Background(), &logical.BackendConfig{
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: 1000 * time.Second,
MaxLeaseTTLVal: 1800 * time.Second,
},
StorageView: storage,
})
if err != nil {
t.Fatalf("error: %s", err)
}
if err := b.Initialize(context.Background(), &logical.InitializationRequest{
Storage: storage,
}); err != nil {
t.Fatalf("error: %s", err)
}
return b
}
// Test the certificates being registered to the backend
func TestBackend_CertWrites(t *testing.T) {
// CA cert
ca1, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
// Non CA Cert
ca2, err := ioutil.ReadFile("test-fixtures/keys/cert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
// Non CA cert without TLS web client authentication
ca3, err := ioutil.ReadFile("test-fixtures/noclientauthcert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
tc := logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "aaa", ca1, "foo", allowed{}, false),
testAccStepCert(t, "bbb", ca2, "foo", allowed{}, false),
testAccStepCert(t, "ccc", ca3, "foo", allowed{}, true),
},
}
tc.Steps = append(tc.Steps, testAccStepListCerts(t, []string{"aaa", "bbb"})...)
logicaltest.Test(t, tc)
}
// Test a client trusted by a CA
func TestBackend_basic_CA(t *testing.T) {
connState, err := testConnState("test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{}, false),
testAccStepLogin(t, connState),
testAccStepCertLease(t, "web", ca, "foo"),
testAccStepCertTTL(t, "web", ca, "foo"),
testAccStepLogin(t, connState),
testAccStepCertMaxTTL(t, "web", ca, "foo"),
testAccStepLogin(t, connState),
testAccStepCertNoLease(t, "web", ca, "foo"),
testAccStepLoginDefaultLease(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "*.example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "*.invalid.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test CRL behavior
func TestBackend_Basic_CRLs(t *testing.T) {
connState, err := testConnState("test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
crl, err := ioutil.ReadFile("test-fixtures/root/root.crl")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCertNoLease(t, "web", ca, "foo"),
testAccStepLoginDefaultLease(t, connState),
testAccStepAddCRL(t, crl, connState),
testAccStepReadCRL(t, connState),
testAccStepLoginInvalid(t, connState),
testAccStepDeleteCRL(t, connState),
testAccStepLoginDefaultLease(t, connState),
},
})
}
// Test a self-signed client (root CA) that is trusted
func TestBackend_basic_singleCert(t *testing.T) {
connState, err := testConnState("test-fixtures/root/rootcacert.pem",
"test-fixtures/root/rootcakey.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "1.2.3.4:invalid"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
func TestBackend_common_name_singleCert(t *testing.T) {
connState, err := testConnState("test-fixtures/root/rootcacert.pem",
"test-fixtures/root/rootcakey.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{common_names: "example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{common_names: "invalid"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "1.2.3.4:invalid"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test a self-signed client with custom ext (root CA) that is trusted
func TestBackend_ext_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawextcert.pem",
"test-fixtures/root/rootcawextkey.pem",
"test-fixtures/root/rootcacert.pem",
)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:A UTF8String Extension"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:*,2.1.1.2:A UTF8*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "1.2.3.45:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:,2.1.1.2:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:A UTF8String Extension"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:*,2.1.1.2:A UTF8*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "1.2.3.45:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:A UTF8String Extension"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:*,2.1.1.2:A UTF8*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "1.2.3.45:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "hex:2.5.29.17:*87047F000002*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "hex:2.5.29.17:*87047F000001*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.5.29.17:"}, false),
testAccStepLogin(t, connState),
testAccStepReadConfig(t, config{EnableIdentityAliasMetadata: false}, connState),
testAccStepCert(t, "web", ca, "foo", allowed{metadata_ext: "2.1.1.1,1.2.3.45"}, false),
testAccStepLoginWithMetadata(t, connState, "web", map[string]string{"2-1-1-1": "A UTF8String Extension"}, false),
testAccStepCert(t, "web", ca, "foo", allowed{metadata_ext: "1.2.3.45"}, false),
testAccStepLoginWithMetadata(t, connState, "web", map[string]string{}, false),
testAccStepSetConfig(t, config{EnableIdentityAliasMetadata: true}, connState),
testAccStepReadConfig(t, config{EnableIdentityAliasMetadata: true}, connState),
testAccStepCert(t, "web", ca, "foo", allowed{metadata_ext: "2.1.1.1,1.2.3.45"}, false),
testAccStepLoginWithMetadata(t, connState, "web", map[string]string{"2-1-1-1": "A UTF8String Extension"}, true),
testAccStepCert(t, "web", ca, "foo", allowed{metadata_ext: "1.2.3.45"}, false),
testAccStepLoginWithMetadata(t, connState, "web", map[string]string{}, true),
},
})
}
// Test a self-signed client with URI alt names (root CA) that is trusted
func TestBackend_dns_singleCert(t *testing.T) {
certTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "example.com",
},
DNSNames: []string{"example.com"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
}
tempDir, connState, err := generateTestCertAndConnState(t, certTemplate)
if tempDir != "" {
defer os.RemoveAll(tempDir)
}
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile(filepath.Join(tempDir, "ca_cert.pem"))
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{dns: "example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "*ample.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "notincert.com"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "abc"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "*.example.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test a self-signed client with URI alt names (root CA) that is trusted
func TestBackend_email_singleCert(t *testing.T) {
certTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "example.com",
},
EmailAddresses: []string{"valid@example.com"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
}
tempDir, connState, err := generateTestCertAndConnState(t, certTemplate)
if tempDir != "" {
defer os.RemoveAll(tempDir)
}
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile(filepath.Join(tempDir, "ca_cert.pem"))
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{emails: "valid@example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "*@example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "invalid@notincert.com"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "abc"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "*.example.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test a self-signed client with OU (root CA) that is trusted
func TestBackend_organizationalUnit_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawoucert.pem",
"test-fixtures/root/rootcawoukey.pem",
"test-fixtures/root/rootcawoucert.pem",
)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcawoucert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{organizational_units: "engineering"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{organizational_units: "eng*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{organizational_units: "engineering,finance"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{organizational_units: "foo"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test a self-signed client with URI alt names (root CA) that is trusted
func TestBackend_uri_singleCert(t *testing.T) {
u, err := url.Parse("spiffe://example.com/host")
if err != nil {
t.Fatal(err)
}
certTemplate := &x509.Certificate{
Subject: pkix.Name{
CommonName: "example.com",
},
DNSNames: []string{"example.com"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
URIs: []*url.URL{u},
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
SerialNumber: big.NewInt(mathrand.Int63()),
NotBefore: time.Now().Add(-30 * time.Second),
NotAfter: time.Now().Add(262980 * time.Hour),
}
tempDir, connState, err := generateTestCertAndConnState(t, certTemplate)
if tempDir != "" {
defer os.RemoveAll(tempDir)
}
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile(filepath.Join(tempDir, "ca_cert.pem"))
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/host"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/invalid"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "abc"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "http://www.google.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test against a collection of matching and non-matching rules
func TestBackend_mixed_constraints(t *testing.T) {
connState, err := testConnState("test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "1unconstrained", ca, "foo", allowed{}, false),
testAccStepCert(t, "2matching", ca, "foo", allowed{names: "*.example.com,whatever"}, false),
testAccStepCert(t, "3invalid", ca, "foo", allowed{names: "invalid"}, false),
testAccStepLogin(t, connState),
// Assumes CertEntries are processed in alphabetical order (due to store.List), so we only match 2matching if 1unconstrained doesn't match
testAccStepLoginWithName(t, connState, "2matching"),
testAccStepLoginWithNameInvalid(t, connState, "3invalid"),
},
})
}
// Test an untrusted client
func TestBackend_untrusted(t *testing.T) {
connState, err := testConnState("test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
CredentialBackend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepLoginInvalid(t, connState),
},
})
}
func TestBackend_validCIDR(t *testing.T) {
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
connState, err := testConnState("test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
name := "web"
boundCIDRs := []string{"127.0.0.1", "128.252.0.0/16"}
addCertReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
Data: map[string]interface{}{
"certificate": string(ca),
"policies": "foo",
"display_name": name,
"allowed_names": "",
"required_extensions": "",
"lease": 1000,
"bound_cidrs": boundCIDRs,
},
Storage: storage,
Connection: &logical.Connection{ConnState: &connState},
}
_, err = b.HandleRequest(context.Background(), addCertReq)
if err != nil {
t.Fatal(err)
}
readCertReq := &logical.Request{
Operation: logical.ReadOperation,
Path: "certs/" + name,
Storage: storage,
Connection: &logical.Connection{ConnState: &connState},
}
readResult, err := b.HandleRequest(context.Background(), readCertReq)
if err != nil {
t.Fatal(err)
}
cidrsResult := readResult.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)
if cidrsResult[0].String() != boundCIDRs[0] ||
cidrsResult[1].String() != boundCIDRs[1] {
t.Fatalf("bound_cidrs couldn't be set correctly, EXPECTED: %v, ACTUAL: %v", boundCIDRs, cidrsResult)
}
loginReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "login",
Unauthenticated: true,
Data: map[string]interface{}{
"name": name,
},
Storage: storage,
Connection: &logical.Connection{ConnState: &connState},
}
// override the remote address with an IPV4 that is authorized
loginReq.Connection.RemoteAddr = "127.0.0.1/32"
_, err = b.HandleRequest(context.Background(), loginReq)
if err != nil {
t.Fatal(err.Error())
}
}
func TestBackend_invalidCIDR(t *testing.T) {
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatal(err)
}
connState, err := testConnState("test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
name := "web"
addCertReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
Data: map[string]interface{}{
"certificate": string(ca),
"policies": "foo",
"display_name": name,
"allowed_names": "",
"required_extensions": "",
"lease": 1000,
"bound_cidrs": []string{"127.0.0.1/32", "128.252.0.0/16"},
},
Storage: storage,
Connection: &logical.Connection{ConnState: &connState},
}
_, err = b.HandleRequest(context.Background(), addCertReq)
if err != nil {
t.Fatal(err)
}
loginReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "login",
Unauthenticated: true,
Data: map[string]interface{}{
"name": name,
},
Storage: storage,
Connection: &logical.Connection{ConnState: &connState},
}
// override the remote address with an IPV4 that isn't authorized
loginReq.Connection.RemoteAddr = "127.0.0.1/8"
_, err = b.HandleRequest(context.Background(), loginReq)
if err == nil {
t.Fatal("expected \"ERROR: permission denied\"")
}
}
func testAccStepAddCRL(t *testing.T, crl []byte, connState tls.ConnectionState) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "crls/test",
ConnState: &connState,
Data: map[string]interface{}{
"crl": crl,
},
}
}
func testAccStepReadCRL(t *testing.T, connState tls.ConnectionState) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "crls/test",
ConnState: &connState,
Check: func(resp *logical.Response) error {
crlInfo := CRLInfo{}
err := mapstructure.Decode(resp.Data, &crlInfo)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(crlInfo.Serials) != 1 {
t.Fatalf("bad: expected CRL with length 1, got %d", len(crlInfo.Serials))
}
if _, ok := crlInfo.Serials["637101449987587619778072672905061040630001617053"]; !ok {
t.Fatalf("bad: expected serial number not found in CRL")
}
return nil
},
}
}
func testAccStepDeleteCRL(t *testing.T, connState tls.ConnectionState) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.DeleteOperation,
Path: "crls/test",
ConnState: &connState,
}
}
func testAccStepSetConfig(t *testing.T, conf config, connState tls.ConnectionState) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "config",
ConnState: &connState,
Data: map[string]interface{}{
"enable_identity_alias_metadata": conf.EnableIdentityAliasMetadata,
},
}
}
func testAccStepReadConfig(t *testing.T, conf config, connState tls.ConnectionState) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "config",
ConnState: &connState,
Check: func(resp *logical.Response) error {
value, ok := resp.Data["enable_identity_alias_metadata"]
if !ok {
t.Fatalf("enable_identity_alias_metadata not found in response")
}
b, ok := value.(bool)
if !ok {
t.Fatalf("bad: expected enable_identity_alias_metadata to be a bool")
}
if b != conf.EnableIdentityAliasMetadata {
t.Fatalf("bad: expected enable_identity_alias_metadata to be %t, got %t", conf.EnableIdentityAliasMetadata, b)
}
return nil
},
}
}
func testAccStepLogin(t *testing.T, connState tls.ConnectionState) logicaltest.TestStep {
return testAccStepLoginWithName(t, connState, "")
}
func testAccStepLoginWithName(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login",
Unauthenticated: true,
ConnState: &connState,
Check: func(resp *logical.Response) error {
if resp.Auth.TTL != 1000*time.Second {
t.Fatalf("bad lease length: %#v", resp.Auth)
}
if certName != "" && resp.Auth.DisplayName != ("mnt-"+certName) {
t.Fatalf("matched the wrong cert: %#v", resp.Auth.DisplayName)
}
fn := logicaltest.TestCheckAuth([]string{"default", "foo"})
return fn(resp)
},
Data: map[string]interface{}{
"name": certName,
},
}
}
func testAccStepLoginDefaultLease(t *testing.T, connState tls.ConnectionState) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login",
Unauthenticated: true,
ConnState: &connState,
Check: func(resp *logical.Response) error {
if resp.Auth.TTL != 1000*time.Second {
t.Fatalf("bad lease length: %#v", resp.Auth)
}
fn := logicaltest.TestCheckAuth([]string{"default", "foo"})
return fn(resp)
},
}
}
func testAccStepLoginWithMetadata(t *testing.T, connState tls.ConnectionState, certName string, metadata map[string]string, expectAliasMetadata bool) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login",
Unauthenticated: true,
ConnState: &connState,
Check: func(resp *logical.Response) error {
// Check for fixed metadata too
metadata["cert_name"] = certName
metadata["common_name"] = connState.PeerCertificates[0].Subject.CommonName
metadata["serial_number"] = connState.PeerCertificates[0].SerialNumber.String()
metadata["subject_key_id"] = certutil.GetHexFormatted(connState.PeerCertificates[0].SubjectKeyId, ":")
metadata["authority_key_id"] = certutil.GetHexFormatted(connState.PeerCertificates[0].AuthorityKeyId, ":")
for key, expected := range metadata {
value, ok := resp.Auth.Metadata[key]
if !ok {
t.Fatalf("missing metadata key: %s", key)
}
if value != expected {
t.Fatalf("expected metadata key %s to equal %s, but got: %s", key, expected, value)
}
if expectAliasMetadata {
value, ok = resp.Auth.Alias.Metadata[key]
if !ok {
t.Fatalf("missing alias metadata key: %s", key)
}
if value != expected {
t.Fatalf("expected metadata key %s to equal %s, but got: %s", key, expected, value)
}
} else {
if len(resp.Auth.Alias.Metadata) > 0 {
t.Fatal("found alias metadata keys, but should not have any")
}
}
}
fn := logicaltest.TestCheckAuth([]string{"default", "foo"})
return fn(resp)
},
Data: map[string]interface{}{
"metadata": metadata,
},
}
}
func testAccStepLoginInvalid(t *testing.T, connState tls.ConnectionState) logicaltest.TestStep {
return testAccStepLoginWithNameInvalid(t, connState, "")
}
func testAccStepLoginWithNameInvalid(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login",
Unauthenticated: true,
ConnState: &connState,
Check: func(resp *logical.Response) error {
if resp.Auth != nil {
return fmt.Errorf("should not be authorized: %#v", resp)
}
return nil
},
Data: map[string]interface{}{
"name": certName,
},
ErrorOk: true,
}
}
func testAccStepListCerts(
t *testing.T, certs []string,
) []logicaltest.TestStep {
return []logicaltest.TestStep{
{
Operation: logical.ListOperation,
Path: "certs",
Check: func(resp *logical.Response) error {
if resp == nil {
return fmt.Errorf("nil response")
}
if resp.Data == nil {
return fmt.Errorf("nil data")
}
if resp.Data["keys"] == interface{}(nil) {
return fmt.Errorf("nil keys")
}
keys := resp.Data["keys"].([]string)
if !reflect.DeepEqual(keys, certs) {
return fmt.Errorf("mismatch: keys is %#v, certs is %#v", keys, certs)
}
return nil
},
}, {
Operation: logical.ListOperation,
Path: "certs/",
Check: func(resp *logical.Response) error {
if resp == nil {
return fmt.Errorf("nil response")
}
if resp.Data == nil {
return fmt.Errorf("nil data")
}
if resp.Data["keys"] == interface{}(nil) {
return fmt.Errorf("nil keys")
}
keys := resp.Data["keys"].([]string)
if !reflect.DeepEqual(keys, certs) {
return fmt.Errorf("mismatch: keys is %#v, certs is %#v", keys, certs)
}
return nil
},
},
}
}
type allowed struct {
names string // allowed names in the certificate, looks at common, name, dns, email [depricated]
common_names string // allowed common names in the certificate
dns string // allowed dns names in the SAN extension of the certificate
emails string // allowed email names in SAN extension of the certificate
uris string // allowed uris in SAN extension of the certificate
organizational_units string // allowed OUs in the certificate
ext string // required extensions in the certificate
metadata_ext string // allowed metadata extensions to add to identity alias
}
func testAccStepCert(t *testing.T, name string, cert []byte, policies string, testData allowed, expectError bool) logicaltest.TestStep {
return testAccStepCertWithExtraParams(t, name, cert, policies, testData, expectError, nil)
}
func testAccStepCertWithExtraParams(t *testing.T, name string, cert []byte, policies string, testData allowed, expectError bool, extraParams map[string]interface{}) logicaltest.TestStep {
data := map[string]interface{}{
"certificate": string(cert),
"policies": policies,
"display_name": name,
"allowed_names": testData.names,
"allowed_common_names": testData.common_names,
"allowed_dns_sans": testData.dns,
"allowed_email_sans": testData.emails,
"allowed_uri_sans": testData.uris,
"allowed_organizational_units": testData.organizational_units,
"required_extensions": testData.ext,
"allowed_metadata_extensions": testData.metadata_ext,
"lease": 1000,
}
for k, v := range extraParams {
data[k] = v
}
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
ErrorOk: expectError,
Data: data,
Check: func(resp *logical.Response) error {
if resp == nil && expectError {
return fmt.Errorf("expected error but received nil")
}
return nil
},
}
}
func testAccStepReadCertPolicy(t *testing.T, name string, expectError bool, expected map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "certs/" + name,
ErrorOk: expectError,
Data: nil,
Check: func(resp *logical.Response) error {
if (resp == nil || len(resp.Data) == 0) && expectError {
return fmt.Errorf("expected error but received nil")
}
for key, expectedValue := range expected {
actualValue := resp.Data[key]
if expectedValue != actualValue {
return fmt.Errorf("Expected to get [%v]=[%v] but read [%v]=[%v] from server for certs/%v: %v", key, expectedValue, key, actualValue, name, resp)
}
}
return nil
},
}
}
func testAccStepCertLease(
t *testing.T, name string, cert []byte, policies string,
) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
Data: map[string]interface{}{
"certificate": string(cert),
"policies": policies,
"display_name": name,
"lease": 1000,
},
}
}
func testAccStepCertTTL(
t *testing.T, name string, cert []byte, policies string,
) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
Data: map[string]interface{}{
"certificate": string(cert),
"policies": policies,
"display_name": name,
"ttl": "1000s",
},
}
}
func testAccStepCertMaxTTL(
t *testing.T, name string, cert []byte, policies string,
) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
Data: map[string]interface{}{
"certificate": string(cert),
"policies": policies,
"display_name": name,
"ttl": "1000s",
"max_ttl": "1200s",
},
}
}
func testAccStepCertNoLease(
t *testing.T, name string, cert []byte, policies string,
) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
Data: map[string]interface{}{
"certificate": string(cert),
"policies": policies,
"display_name": name,
},
}
}
func testConnState(certPath, keyPath, rootCertPath string) (tls.ConnectionState, error) {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return tls.ConnectionState{}, err
}
rootConfig := &rootcerts.Config{
CAFile: rootCertPath,
}
rootCAs, err := rootcerts.LoadCACerts(rootConfig)
if err != nil {
return tls.ConnectionState{}, err
}
listenConf := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequestClientCert,
InsecureSkipVerify: false,
RootCAs: rootCAs,
}
dialConf := listenConf.Clone()
// start a server
list, err := tls.Listen("tcp", "127.0.0.1:0", listenConf)
if err != nil {
return tls.ConnectionState{}, err
}
defer list.Close()
// Accept connections.
serverErrors := make(chan error, 1)
connState := make(chan tls.ConnectionState)
go func() {
defer close(connState)
serverConn, err := list.Accept()
serverErrors <- err
if err != nil {
close(serverErrors)
return
}
defer serverConn.Close()
// Read the ping
buf := make([]byte, 4)
_, err = serverConn.Read(buf)
if (err != nil) && (err != io.EOF) {
serverErrors <- err
close(serverErrors)
return
} else {
// EOF is a reasonable error condition, so swallow it.
serverErrors <- nil
}
close(serverErrors)
connState <- serverConn.(*tls.Conn).ConnectionState()
}()
// Establish a connection from the client side and write a few bytes.
clientErrors := make(chan error, 1)
go func() {
addr := list.Addr().String()
conn, err := tls.Dial("tcp", addr, dialConf)
clientErrors <- err
if err != nil {
close(clientErrors)
return
}
defer conn.Close()
// Write ping
_, err = conn.Write([]byte("ping"))
clientErrors <- err
close(clientErrors)
}()
for err = range clientErrors {
if err != nil {
return tls.ConnectionState{}, fmt.Errorf("error in client goroutine:%v", err)
}
}
for err = range serverErrors {
if err != nil {
return tls.ConnectionState{}, fmt.Errorf("error in server goroutine:%v", err)
}
}
// Grab the current state
return <-connState, nil
}
func Test_Renew(t *testing.T) {
storage := &logical.InmemStorage{}
lb, err := Factory(context.Background(), &logical.BackendConfig{
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: 300 * time.Second,
MaxLeaseTTLVal: 1800 * time.Second,
},
StorageView: storage,
})
if err != nil {
t.Fatalf("error: %s", err)
}
b := lb.(*backend)
connState, err := testConnState("test-fixtures/keys/cert.pem",
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatal(err)
}
req := &logical.Request{
Connection: &logical.Connection{
ConnState: &connState,
},
Storage: storage,
Auth: &logical.Auth{},
}
fd := &framework.FieldData{
Raw: map[string]interface{}{
"name": "test",
"certificate": ca,
"policies": "foo,bar",
},
Schema: pathCerts(b).Fields,
}
resp, err := b.pathCertWrite(context.Background(), req, fd)
if err != nil {
t.Fatal(err)
}
empty_login_fd := &framework.FieldData{
Raw: map[string]interface{}{},
Schema: pathLogin(b).Fields,
}
resp, err = b.pathLogin(context.Background(), req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
if resp.IsError() {
t.Fatalf("got error: %#v", *resp)
}
req.Auth.InternalData = resp.Auth.InternalData
req.Auth.Metadata = resp.Auth.Metadata
req.Auth.LeaseOptions = resp.Auth.LeaseOptions
req.Auth.Policies = resp.Auth.Policies
req.Auth.TokenPolicies = req.Auth.Policies
req.Auth.Period = resp.Auth.Period
// Normal renewal
resp, err = b.pathLoginRenew(context.Background(), req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("got nil response from renew")
}
if resp.IsError() {
t.Fatalf("got error: %#v", *resp)
}
// Change the policies -- this should fail
fd.Raw["policies"] = "zip,zap"
resp, err = b.pathCertWrite(context.Background(), req, fd)
if err != nil {
t.Fatal(err)
}
resp, err = b.pathLoginRenew(context.Background(), req, empty_login_fd)
if err == nil {
t.Fatal("expected error")
}
// Put the policies back, this should be okay
fd.Raw["policies"] = "bar,foo"
resp, err = b.pathCertWrite(context.Background(), req, fd)
if err != nil {
t.Fatal(err)
}
resp, err = b.pathLoginRenew(context.Background(), req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("got nil response from renew")
}
if resp.IsError() {
t.Fatalf("got error: %#v", *resp)
}
// Add period value to cert entry
period := 350 * time.Second
fd.Raw["period"] = period.String()
resp, err = b.pathCertWrite(context.Background(), req, fd)
if err != nil {
t.Fatal(err)
}
resp, err = b.pathLoginRenew(context.Background(), req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("got nil response from renew")
}
if resp.IsError() {
t.Fatalf("got error: %#v", *resp)
}
if resp.Auth.Period != period {
t.Fatalf("expected a period value of %s in the response, got: %s", period, resp.Auth.Period)
}
// Delete CA, make sure we can't renew
resp, err = b.pathCertDelete(context.Background(), req, fd)
if err != nil {
t.Fatal(err)
}
resp, err = b.pathLoginRenew(context.Background(), req, empty_login_fd)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("got nil response from renew")
}
if !resp.IsError() {
t.Fatal("expected error")
}
}
func TestBackend_CertUpgrade(t *testing.T) {
s := &logical.InmemStorage{}
config := logical.TestBackendConfig()
config.StorageView = s
ctx := context.Background()
b := Backend()
if b == nil {
t.Fatalf("failed to create backend")
}
if err := b.Setup(ctx, config); err != nil {
t.Fatal(err)
}
foo := &CertEntry{
Policies: []string{"foo"},
Period: time.Second,
TTL: time.Second,
MaxTTL: time.Second,
BoundCIDRs: []*sockaddr.SockAddrMarshaler{{SockAddr: sockaddr.MustIPAddr("127.0.0.1")}},
}
entry, err := logical.StorageEntryJSON("cert/foo", foo)
if err != nil {
t.Fatal(err)
}
err = s.Put(ctx, entry)
if err != nil {
t.Fatal(err)
}
certEntry, err := b.Cert(ctx, s, "foo")
if err != nil {
t.Fatal(err)
}
exp := &CertEntry{
Policies: []string{"foo"},
Period: time.Second,
TTL: time.Second,
MaxTTL: time.Second,
BoundCIDRs: []*sockaddr.SockAddrMarshaler{{SockAddr: sockaddr.MustIPAddr("127.0.0.1")}},
TokenParams: tokenutil.TokenParams{
TokenPolicies: []string{"foo"},
TokenPeriod: time.Second,
TokenTTL: time.Second,
TokenMaxTTL: time.Second,
TokenBoundCIDRs: []*sockaddr.SockAddrMarshaler{{SockAddr: sockaddr.MustIPAddr("127.0.0.1")}},
},
}
if diff := deep.Equal(certEntry, exp); diff != nil {
t.Fatal(diff)
}
}