github.com/outbrain/consul@v1.4.5/agent/connect/ca/provider_vault_test.go (about) 1 package ca 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/hashicorp/consul/agent/connect" 12 "github.com/hashicorp/consul/agent/structs" 13 vaultapi "github.com/hashicorp/vault/api" 14 "github.com/hashicorp/vault/builtin/logical/pki" 15 vaulthttp "github.com/hashicorp/vault/http" 16 "github.com/hashicorp/vault/vault" 17 "github.com/stretchr/testify/require" 18 ) 19 20 var vaultLock sync.Mutex 21 22 func testVaultCluster(t *testing.T) (*VaultProvider, *vault.Core, net.Listener) { 23 return testVaultClusterWithConfig(t, true, nil) 24 } 25 26 func testVaultClusterWithConfig(t *testing.T, isRoot bool, rawConf map[string]interface{}) (*VaultProvider, *vault.Core, net.Listener) { 27 vaultLock.Lock() 28 defer vaultLock.Unlock() 29 30 if err := vault.AddTestLogicalBackend("pki", pki.Factory); err != nil { 31 t.Fatal(err) 32 } 33 core, _, token := vault.TestCoreUnsealedRaw(t) 34 35 ln, addr := vaulthttp.TestServer(t, core) 36 37 conf := map[string]interface{}{ 38 "Address": addr, 39 "Token": token, 40 "RootPKIPath": "pki-root/", 41 "IntermediatePKIPath": "pki-intermediate/", 42 // Tests duration parsing after msgpack type mangling during raft apply. 43 "LeafCertTTL": []uint8("72h"), 44 } 45 for k, v := range rawConf { 46 conf[k] = v 47 } 48 49 require := require.New(t) 50 provider := &VaultProvider{} 51 require.NoError(provider.Configure("asdf", isRoot, conf)) 52 if isRoot { 53 require.NoError(provider.GenerateRoot()) 54 _, err := provider.GenerateIntermediate() 55 require.NoError(err) 56 } 57 58 return provider, core, ln 59 } 60 61 func TestVaultCAProvider_VaultTLSConfig(t *testing.T) { 62 config := &structs.VaultCAProviderConfig{ 63 CAFile: "/capath/ca.pem", 64 CAPath: "/capath/", 65 CertFile: "/certpath/cert.pem", 66 KeyFile: "/certpath/key.pem", 67 TLSServerName: "server.name", 68 TLSSkipVerify: true, 69 } 70 tlsConfig := vaultTLSConfig(config) 71 require := require.New(t) 72 require.Equal(config.CAFile, tlsConfig.CACert) 73 require.Equal(config.CAPath, tlsConfig.CAPath) 74 require.Equal(config.CertFile, tlsConfig.ClientCert) 75 require.Equal(config.KeyFile, tlsConfig.ClientKey) 76 require.Equal(config.TLSServerName, tlsConfig.TLSServerName) 77 require.Equal(config.TLSSkipVerify, tlsConfig.Insecure) 78 } 79 80 func TestVaultCAProvider_Bootstrap(t *testing.T) { 81 t.Parallel() 82 83 require := require.New(t) 84 provider, core, listener := testVaultCluster(t) 85 defer core.Shutdown() 86 defer listener.Close() 87 client, err := vaultapi.NewClient(&vaultapi.Config{ 88 Address: "http://" + listener.Addr().String(), 89 }) 90 require.NoError(err) 91 client.SetToken(provider.config.Token) 92 93 cases := []struct { 94 certFunc func() (string, error) 95 backendPath string 96 }{ 97 { 98 certFunc: provider.ActiveRoot, 99 backendPath: "pki-root/", 100 }, 101 { 102 certFunc: provider.ActiveIntermediate, 103 backendPath: "pki-intermediate/", 104 }, 105 } 106 107 // Verify the root and intermediate certs match the ones in the vault backends 108 for _, tc := range cases { 109 cert, err := tc.certFunc() 110 require.NoError(err) 111 req := client.NewRequest("GET", "/v1/"+tc.backendPath+"ca/pem") 112 resp, err := client.RawRequest(req) 113 require.NoError(err) 114 bytes, err := ioutil.ReadAll(resp.Body) 115 require.NoError(err) 116 require.Equal(cert, string(bytes)) 117 118 // Should be a valid CA cert 119 parsed, err := connect.ParseCert(cert) 120 require.NoError(err) 121 require.True(parsed.IsCA) 122 require.Len(parsed.URIs, 1) 123 require.Equal(parsed.URIs[0].String(), fmt.Sprintf("spiffe://%s.consul", provider.clusterId)) 124 } 125 } 126 127 func TestVaultCAProvider_SignLeaf(t *testing.T) { 128 t.Parallel() 129 130 require := require.New(t) 131 provider, core, listener := testVaultClusterWithConfig(t, true, map[string]interface{}{ 132 "LeafCertTTL": "1h", 133 }) 134 defer core.Shutdown() 135 defer listener.Close() 136 client, err := vaultapi.NewClient(&vaultapi.Config{ 137 Address: "http://" + listener.Addr().String(), 138 }) 139 require.NoError(err) 140 client.SetToken(provider.config.Token) 141 142 spiffeService := &connect.SpiffeIDService{ 143 Host: "node1", 144 Namespace: "default", 145 Datacenter: "dc1", 146 Service: "foo", 147 } 148 149 // Generate a leaf cert for the service. 150 var firstSerial uint64 151 { 152 raw, _ := connect.TestCSR(t, spiffeService) 153 154 csr, err := connect.ParseCSR(raw) 155 require.NoError(err) 156 157 cert, err := provider.Sign(csr) 158 require.NoError(err) 159 160 parsed, err := connect.ParseCert(cert) 161 require.NoError(err) 162 require.Equal(parsed.URIs[0], spiffeService.URI()) 163 firstSerial = parsed.SerialNumber.Uint64() 164 165 // Ensure the cert is valid now and expires within the correct limit. 166 now := time.Now() 167 require.True(parsed.NotAfter.Sub(now) < time.Hour) 168 require.True(parsed.NotBefore.Before(now)) 169 } 170 171 // Generate a new cert for another service and make sure 172 // the serial number is unique. 173 spiffeService.Service = "bar" 174 { 175 raw, _ := connect.TestCSR(t, spiffeService) 176 177 csr, err := connect.ParseCSR(raw) 178 require.NoError(err) 179 180 cert, err := provider.Sign(csr) 181 require.NoError(err) 182 183 parsed, err := connect.ParseCert(cert) 184 require.NoError(err) 185 require.Equal(parsed.URIs[0], spiffeService.URI()) 186 require.NotEqual(firstSerial, parsed.SerialNumber.Uint64()) 187 188 // Ensure the cert is valid now and expires within the correct limit. 189 require.True(parsed.NotAfter.Sub(time.Now()) < time.Hour) 190 require.True(parsed.NotBefore.Before(time.Now())) 191 } 192 } 193 194 func TestVaultCAProvider_CrossSignCA(t *testing.T) { 195 t.Parallel() 196 197 provider1, core1, listener1 := testVaultCluster(t) 198 defer core1.Shutdown() 199 defer listener1.Close() 200 201 provider2, core2, listener2 := testVaultCluster(t) 202 defer core2.Shutdown() 203 defer listener2.Close() 204 205 testCrossSignProviders(t, provider1, provider2) 206 } 207 208 func TestVaultProvider_SignIntermediate(t *testing.T) { 209 t.Parallel() 210 211 provider1, core1, listener1 := testVaultCluster(t) 212 defer core1.Shutdown() 213 defer listener1.Close() 214 215 provider2, core2, listener2 := testVaultClusterWithConfig(t, false, nil) 216 defer core2.Shutdown() 217 defer listener2.Close() 218 219 testSignIntermediateCrossDC(t, provider1, provider2) 220 } 221 222 func TestVaultProvider_SignIntermediateConsul(t *testing.T) { 223 t.Parallel() 224 225 require := require.New(t) 226 227 // primary = Vault, secondary = Consul 228 { 229 provider1, core, listener := testVaultCluster(t) 230 defer core.Shutdown() 231 defer listener.Close() 232 233 conf := testConsulCAConfig() 234 delegate := newMockDelegate(t, conf) 235 provider2 := &ConsulProvider{Delegate: delegate} 236 require.NoError(provider2.Configure(conf.ClusterID, false, conf.Config)) 237 238 testSignIntermediateCrossDC(t, provider1, provider2) 239 } 240 241 // primary = Consul, secondary = Vault 242 { 243 conf := testConsulCAConfig() 244 delegate := newMockDelegate(t, conf) 245 provider1 := &ConsulProvider{Delegate: delegate} 246 require.NoError(provider1.Configure(conf.ClusterID, true, conf.Config)) 247 require.NoError(provider1.GenerateRoot()) 248 249 provider2, core, listener := testVaultClusterWithConfig(t, false, nil) 250 defer core.Shutdown() 251 defer listener.Close() 252 253 testSignIntermediateCrossDC(t, provider1, provider2) 254 } 255 }