2023-03-28 19:12:30 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2023-08-11 13:12:13 +00:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-28 19:12:30 +00:00
|
|
|
|
2018-12-19 08:22:49 +00:00
|
|
|
package create
|
|
|
|
|
|
|
|
import (
|
2020-01-31 16:12:36 +00:00
|
|
|
"crypto"
|
|
|
|
"crypto/x509"
|
2021-12-08 19:16:36 +00:00
|
|
|
"io/fs"
|
2020-01-31 16:12:36 +00:00
|
|
|
"net"
|
2019-01-23 20:48:57 +00:00
|
|
|
"os"
|
2018-12-19 08:22:49 +00:00
|
|
|
"strings"
|
|
|
|
"testing"
|
2019-01-23 20:48:57 +00:00
|
|
|
|
|
|
|
"github.com/mitchellh/cli"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2022-10-21 19:58:06 +00:00
|
|
|
"github.com/hashicorp/consul/agent/connect"
|
|
|
|
"github.com/hashicorp/consul/sdk/testutil"
|
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
caCreate "github.com/hashicorp/consul/command/tls/ca/create"
|
2018-12-19 08:22:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestValidateCommand_noTabs(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
if strings.ContainsRune(New(nil).Help(), '\t') {
|
|
|
|
t.Fatal("help has tabs")
|
|
|
|
}
|
|
|
|
}
|
2019-01-23 20:48:57 +00:00
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
func TestTlsCertCreateCommand_InvalidArgs(t *testing.T) {
|
|
|
|
t.Parallel()
|
2019-01-23 20:48:57 +00:00
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
type testcase struct {
|
|
|
|
args []string
|
|
|
|
expectErr string
|
|
|
|
}
|
|
|
|
|
|
|
|
cases := map[string]testcase{
|
|
|
|
"no args (ca/key inferred)": {[]string{},
|
|
|
|
"Please provide either -server, -client, or -cli"},
|
|
|
|
"no ca": {[]string{"-ca", "", "-key", ""},
|
|
|
|
"Please provide the ca"},
|
|
|
|
"no key": {[]string{"-ca", "foo.pem", "-key", ""},
|
|
|
|
"Please provide the key"},
|
|
|
|
|
|
|
|
"server+client+cli": {[]string{"-server", "-client", "-cli"},
|
|
|
|
"Please provide either -server, -client, or -cli"},
|
|
|
|
"server+client": {[]string{"-server", "-client"},
|
|
|
|
"Please provide either -server, -client, or -cli"},
|
|
|
|
"server+cli": {[]string{"-server", "-cli"},
|
|
|
|
"Please provide either -server, -client, or -cli"},
|
|
|
|
"client+cli": {[]string{"-client", "-cli"},
|
|
|
|
"Please provide either -server, -client, or -cli"},
|
2020-03-09 20:59:02 +00:00
|
|
|
|
|
|
|
"client+node": {[]string{"-client", "-node", "foo"},
|
|
|
|
"-node requires -server"},
|
|
|
|
"cli+node": {[]string{"-cli", "-node", "foo"},
|
|
|
|
"-node requires -server"},
|
2020-01-31 16:12:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range cases {
|
|
|
|
tc := tc
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
ui := cli.NewMockUi()
|
|
|
|
cmd := New(ui)
|
|
|
|
require.NotEqual(t, 0, cmd.Run(tc.args))
|
|
|
|
got := ui.ErrorWriter.String()
|
|
|
|
if tc.expectErr == "" {
|
|
|
|
require.NotEmpty(t, got) // don't care
|
|
|
|
} else {
|
|
|
|
require.Contains(t, got, tc.expectErr)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-01-23 20:48:57 +00:00
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
func TestTlsCertCreateCommand_fileCreate(t *testing.T) {
|
2019-01-23 20:48:57 +00:00
|
|
|
testDir := testutil.TempDir(t, "tls")
|
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
defer switchToTempDir(t, testDir)()
|
2019-01-23 20:48:57 +00:00
|
|
|
|
|
|
|
// Setup CA keys
|
|
|
|
createCA(t, "consul")
|
2020-01-31 16:12:36 +00:00
|
|
|
createCA(t, "nomad")
|
2019-01-23 20:48:57 +00:00
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
type testcase struct {
|
|
|
|
name string
|
|
|
|
typ string
|
|
|
|
args []string
|
|
|
|
certPath string
|
|
|
|
keyPath string
|
|
|
|
expectCN string
|
|
|
|
expectDNS []string
|
|
|
|
expectIP []net.IP
|
|
|
|
}
|
2019-01-23 20:48:57 +00:00
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
// The following subtests must run serially.
|
|
|
|
cases := []testcase{
|
|
|
|
{"server0",
|
|
|
|
"server",
|
|
|
|
[]string{"-server"},
|
|
|
|
"dc1-server-consul-0.pem",
|
|
|
|
"dc1-server-consul-0-key.pem",
|
|
|
|
"server.dc1.consul",
|
|
|
|
[]string{
|
|
|
|
"server.dc1.consul",
|
|
|
|
"localhost",
|
|
|
|
},
|
|
|
|
[]net.IP{{127, 0, 0, 1}},
|
|
|
|
},
|
2020-03-09 20:59:02 +00:00
|
|
|
{"server1-with-node",
|
2020-01-31 16:12:36 +00:00
|
|
|
"server",
|
2020-03-09 20:59:02 +00:00
|
|
|
[]string{"-server", "-node", "mysrv"},
|
2020-01-31 16:12:36 +00:00
|
|
|
"dc1-server-consul-1.pem",
|
|
|
|
"dc1-server-consul-1-key.pem",
|
|
|
|
"server.dc1.consul",
|
|
|
|
[]string{
|
2020-03-09 20:59:02 +00:00
|
|
|
"mysrv.server.dc1.consul",
|
2020-01-31 16:12:36 +00:00
|
|
|
"server.dc1.consul",
|
|
|
|
"localhost",
|
|
|
|
},
|
|
|
|
[]net.IP{{127, 0, 0, 1}},
|
|
|
|
},
|
|
|
|
{"server0-dc2-altdomain",
|
|
|
|
"server",
|
|
|
|
[]string{"-server", "-dc", "dc2", "-domain", "nomad"},
|
|
|
|
"dc2-server-nomad-0.pem",
|
|
|
|
"dc2-server-nomad-0-key.pem",
|
|
|
|
"server.dc2.nomad",
|
|
|
|
[]string{
|
|
|
|
"server.dc2.nomad",
|
|
|
|
"localhost",
|
|
|
|
},
|
|
|
|
[]net.IP{{127, 0, 0, 1}},
|
|
|
|
},
|
|
|
|
{"client0",
|
|
|
|
"client",
|
|
|
|
[]string{"-client"},
|
|
|
|
"dc1-client-consul-0.pem",
|
|
|
|
"dc1-client-consul-0-key.pem",
|
|
|
|
"client.dc1.consul",
|
|
|
|
[]string{
|
|
|
|
"client.dc1.consul",
|
|
|
|
"localhost",
|
|
|
|
},
|
|
|
|
[]net.IP{{127, 0, 0, 1}},
|
|
|
|
},
|
|
|
|
{"client1",
|
|
|
|
"client",
|
|
|
|
[]string{"-client"},
|
|
|
|
"dc1-client-consul-1.pem",
|
|
|
|
"dc1-client-consul-1-key.pem",
|
|
|
|
"client.dc1.consul",
|
|
|
|
[]string{
|
|
|
|
"client.dc1.consul",
|
|
|
|
"localhost",
|
|
|
|
},
|
|
|
|
[]net.IP{{127, 0, 0, 1}},
|
|
|
|
},
|
|
|
|
{"client0-dc2-altdomain",
|
|
|
|
"client",
|
|
|
|
[]string{"-client", "-dc", "dc2", "-domain", "nomad"},
|
|
|
|
"dc2-client-nomad-0.pem",
|
|
|
|
"dc2-client-nomad-0-key.pem",
|
|
|
|
"client.dc2.nomad",
|
|
|
|
[]string{
|
|
|
|
"client.dc2.nomad",
|
|
|
|
"localhost",
|
|
|
|
},
|
|
|
|
[]net.IP{{127, 0, 0, 1}},
|
|
|
|
},
|
|
|
|
{"cli0",
|
|
|
|
"cli",
|
|
|
|
[]string{"-cli"},
|
|
|
|
"dc1-cli-consul-0.pem",
|
|
|
|
"dc1-cli-consul-0-key.pem",
|
|
|
|
"cli.dc1.consul",
|
|
|
|
[]string{
|
|
|
|
"cli.dc1.consul",
|
|
|
|
"localhost",
|
|
|
|
},
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{"cli1",
|
|
|
|
"cli",
|
|
|
|
[]string{"-cli"},
|
|
|
|
"dc1-cli-consul-1.pem",
|
|
|
|
"dc1-cli-consul-1-key.pem",
|
|
|
|
"cli.dc1.consul",
|
|
|
|
[]string{
|
|
|
|
"cli.dc1.consul",
|
|
|
|
"localhost",
|
|
|
|
},
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{"cli0-dc2-altdomain",
|
|
|
|
"cli",
|
|
|
|
[]string{"-cli", "-dc", "dc2", "-domain", "nomad"},
|
|
|
|
"dc2-cli-nomad-0.pem",
|
|
|
|
"dc2-cli-nomad-0-key.pem",
|
|
|
|
"cli.dc2.nomad",
|
|
|
|
[]string{
|
|
|
|
"cli.dc2.nomad",
|
|
|
|
"localhost",
|
|
|
|
},
|
|
|
|
nil,
|
|
|
|
},
|
2019-01-23 20:48:57 +00:00
|
|
|
}
|
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
for _, tc := range cases {
|
|
|
|
tc := tc
|
|
|
|
require.True(t, t.Run(tc.name, func(t *testing.T) {
|
|
|
|
ui := cli.NewMockUi()
|
|
|
|
cmd := New(ui)
|
|
|
|
require.Equal(t, 0, cmd.Run(tc.args))
|
|
|
|
require.Equal(t, "", ui.ErrorWriter.String())
|
|
|
|
|
|
|
|
cert, _ := expectFiles(t, tc.certPath, tc.keyPath)
|
|
|
|
require.Equal(t, tc.expectCN, cert.Subject.CommonName)
|
|
|
|
require.True(t, cert.BasicConstraintsValid)
|
|
|
|
require.Equal(t, x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment, cert.KeyUsage)
|
|
|
|
switch tc.typ {
|
|
|
|
case "server":
|
|
|
|
require.Equal(t,
|
|
|
|
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
|
|
|
cert.ExtKeyUsage)
|
|
|
|
case "client":
|
|
|
|
require.Equal(t,
|
|
|
|
[]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
|
|
|
cert.ExtKeyUsage)
|
|
|
|
case "cli":
|
|
|
|
require.Len(t, cert.ExtKeyUsage, 0)
|
|
|
|
}
|
|
|
|
require.False(t, cert.IsCA)
|
|
|
|
require.Equal(t, tc.expectDNS, cert.DNSNames)
|
|
|
|
require.Equal(t, tc.expectIP, cert.IPAddresses)
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
2019-01-23 20:48:57 +00:00
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
func expectFiles(t *testing.T, certPath, keyPath string) (*x509.Certificate, crypto.Signer) {
|
|
|
|
t.Helper()
|
2019-01-23 20:48:57 +00:00
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
require.FileExists(t, certPath)
|
|
|
|
require.FileExists(t, keyPath)
|
2019-01-23 20:48:57 +00:00
|
|
|
|
2021-12-08 19:16:36 +00:00
|
|
|
fi, err := os.Stat(keyPath)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("should not happen", err)
|
|
|
|
}
|
|
|
|
if want, have := fs.FileMode(0600), fi.Mode().Perm(); want != have {
|
|
|
|
t.Fatalf("private key file %s: permissions: want: %o; have: %o", keyPath, want, have)
|
|
|
|
}
|
|
|
|
|
2022-11-10 16:26:01 +00:00
|
|
|
certData, err := os.ReadFile(certPath)
|
2020-01-31 16:12:36 +00:00
|
|
|
require.NoError(t, err)
|
2022-11-10 16:26:01 +00:00
|
|
|
keyData, err := os.ReadFile(keyPath)
|
2020-01-31 16:12:36 +00:00
|
|
|
require.NoError(t, err)
|
2019-01-23 20:48:57 +00:00
|
|
|
|
|
|
|
cert, err := connect.ParseCert(string(certData))
|
2020-01-31 16:12:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, cert)
|
2019-01-23 20:48:57 +00:00
|
|
|
|
|
|
|
signer, err := connect.ParseSigner(string(keyData))
|
2020-01-31 16:12:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, signer)
|
2019-01-23 20:48:57 +00:00
|
|
|
|
2020-01-31 16:12:36 +00:00
|
|
|
return cert, signer
|
2019-01-23 20:48:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func createCA(t *testing.T, domain string) {
|
2020-01-31 16:12:36 +00:00
|
|
|
t.Helper()
|
|
|
|
|
2019-01-23 20:48:57 +00:00
|
|
|
ui := cli.NewMockUi()
|
2020-01-31 16:12:36 +00:00
|
|
|
caCmd := caCreate.New(ui)
|
2019-01-23 20:48:57 +00:00
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-domain=" + domain,
|
|
|
|
}
|
|
|
|
|
|
|
|
require.Equal(t, 0, caCmd.Run(args))
|
|
|
|
require.Equal(t, "", ui.ErrorWriter.String())
|
2020-01-31 16:12:36 +00:00
|
|
|
|
|
|
|
require.FileExists(t, "consul-agent-ca.pem")
|
|
|
|
}
|
|
|
|
|
|
|
|
// switchToTempDir is meant to be used in a defer statement like:
|
|
|
|
//
|
2022-10-21 19:58:06 +00:00
|
|
|
// defer switchToTempDir(t, testDir)()
|
2020-01-31 16:12:36 +00:00
|
|
|
//
|
|
|
|
// This exploits the fact that the body of a defer is evaluated
|
|
|
|
// EXCEPT for the final function call invocation inline with the code
|
|
|
|
// where it is found. Only the final evaluation happens in the defer
|
|
|
|
// at a later time. In this case it means we switch to the temp
|
|
|
|
// directory immediately and defer switching back in one line of test
|
|
|
|
// code.
|
|
|
|
func switchToTempDir(t *testing.T, testDir string) func() {
|
|
|
|
previousDirectory, err := os.Getwd()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, os.Chdir(testDir))
|
|
|
|
return func() {
|
|
|
|
os.Chdir(previousDirectory)
|
|
|
|
}
|
2019-01-23 20:48:57 +00:00
|
|
|
}
|