cli: add -dev-consul and -dev-vault agent mode (#19327)

The `-dev-consul` and `-dev-vault` flags add default identities and
configuration to the Nomad agent to connect and use the workload
identity integration with Consul and Vault.
This commit is contained in:
Luiz Aoqui
2023-12-07 11:51:20 -05:00
committed by GitHub
parent 7baf3c012c
commit 27d2ad1baf
4 changed files with 113 additions and 63 deletions

View File

@@ -61,7 +61,6 @@ type Command struct {
}
func (c *Command) readConfig() *Config {
var dev *devModeConfig
var configPath []string
var servers string
var meta []string
@@ -86,8 +85,12 @@ func (c *Command) readConfig() *Config {
// Role options
var devMode bool
var devConnectMode bool
var devConsulMode bool
var devVaultMode bool
flags.BoolVar(&devMode, "dev", false, "")
flags.BoolVar(&devConnectMode, "dev-connect", false, "")
flags.BoolVar(&devConsulMode, "dev-consul", false, "")
flags.BoolVar(&devVaultMode, "dev-vault", false, "")
flags.BoolVar(&cmdConfig.Server.Enabled, "server", false, "")
flags.BoolVar(&cmdConfig.Client.Enabled, "client", false, "")
@@ -221,14 +224,26 @@ func (c *Command) readConfig() *Config {
}
// Load the configuration
dev, err := newDevModeConfig(devMode, devConnectMode)
if err != nil {
c.Ui.Error(err.Error())
return nil
}
var config *Config
if dev != nil {
config = DevConfig(dev)
devConfig := &devModeConfig{
defaultMode: devMode,
connectMode: devConnectMode,
consulMode: devConsulMode,
vaultMode: devVaultMode,
}
if devConfig.enabled() {
err := devConfig.validate()
if err != nil {
c.Ui.Error(err.Error())
return nil
}
err = devConfig.networkConfig()
if err != nil {
c.Ui.Error(err.Error())
return nil
}
config = DevConfig(devConfig)
} else {
config = DefaultConfig()
}
@@ -1402,9 +1417,19 @@ General Options (clients and servers):
-dev-connect
Start the agent in development mode, but bind to a public network
interface rather than localhost for using Consul Connect. This
interface rather than localhost for using Consul Connect. It may be used
with -dev-consul to configure default workload identities for Consul. This
mode is supported only on Linux as root.
-dev-consul
Starts the agent in development mode with a default Consul configuration
for Nomad workload identity. It may be used with -dev-connect to configure
the agent for Consul Service Mesh.
-dev-vault
Starts the agent in development mode with a default Vault configuration
for Nomad workload identity.
Server Options:
-server

View File

@@ -1177,47 +1177,43 @@ type devModeConfig struct {
// mode flags are set at the command line via -dev and -dev-connect
defaultMode bool
connectMode bool
consulMode bool
vaultMode bool
bindAddr string
iface string
}
// newDevModeConfig parses the optional string value of the -dev flag
func newDevModeConfig(devMode, connectMode bool) (*devModeConfig, error) {
if !devMode && !connectMode {
return nil, nil
}
mode := &devModeConfig{}
mode.defaultMode = devMode
if connectMode {
func (mode *devModeConfig) enabled() bool {
return mode.defaultMode || mode.connectMode ||
mode.consulMode || mode.vaultMode
}
func (mode *devModeConfig) validate() error {
if mode.connectMode {
if runtime.GOOS != "linux" {
// strictly speaking -dev-connect only binds to the
// non-localhost interface, but given its purpose
// is to support a feature with network namespaces
// we'll return an error here rather than let the agent
// come up and fail unexpectedly to run jobs
return nil, fmt.Errorf("-dev-connect is only supported on linux.")
return fmt.Errorf("-dev-connect is only supported on linux.")
}
u, err := users.Current()
if err != nil {
return nil, fmt.Errorf(
return fmt.Errorf(
"-dev-connect uses network namespaces and is only supported for root: %v", err)
}
if u.Uid != "0" {
return nil, fmt.Errorf(
return fmt.Errorf(
"-dev-connect uses network namespaces and is only supported for root.")
}
// Ensure Consul is on PATH
if _, err := exec.LookPath("consul"); err != nil {
return nil, fmt.Errorf("-dev-connect requires a 'consul' binary in Nomad's $PATH")
return fmt.Errorf("-dev-connect requires a 'consul' binary in Nomad's $PATH")
}
mode.connectMode = true
}
err := mode.networkConfig()
if err != nil {
return nil, err
}
return mode, nil
return nil
}
func (mode *devModeConfig) networkConfig() error {
@@ -1289,6 +1285,26 @@ func DevConfig(mode *devModeConfig) *Config {
conf.Telemetry.PrometheusMetrics = true
conf.Telemetry.PublishAllocationMetrics = true
conf.Telemetry.PublishNodeMetrics = true
if mode.consulMode {
conf.Consuls[0].ServiceIdentity = &config.WorkloadIdentityConfig{
Audience: []string{"consul.io"},
TTL: pointer.Of(time.Hour),
}
conf.Consuls[0].TaskIdentity = &config.WorkloadIdentityConfig{
Audience: []string{"consul.io"},
TTL: pointer.Of(time.Hour),
}
}
if mode.vaultMode {
conf.Vaults[0].Enabled = pointer.Of(true)
conf.Vaults[0].Addr = "http://localhost:8200"
conf.Vaults[0].DefaultIdentity = &config.WorkloadIdentityConfig{
Audience: []string{"vault.io"},
TTL: pointer.Of(time.Hour),
}
}
return conf
}

View File

@@ -10,7 +10,6 @@ import (
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"time"
@@ -725,59 +724,61 @@ func TestConfig_Listener(t *testing.T) {
}
}
func TestConfig_DevModeFlag(t *testing.T) {
func TestConfig_DevMode_validate(t *testing.T) {
ci.Parallel(t)
cases := []struct {
dev bool
connect bool
expected *devModeConfig
devConfig *devModeConfig
expectedErr string
}{}
if runtime.GOOS != "linux" {
cases = []struct {
dev bool
connect bool
expected *devModeConfig
devConfig *devModeConfig
expectedErr string
}{
{false, false, nil, ""},
{true, false, &devModeConfig{defaultMode: true, connectMode: false}, ""},
{true, true, nil, "-dev-connect is only supported on linux"},
{false, true, nil, "-dev-connect is only supported on linux"},
{
devConfig: &devModeConfig{
connectMode: true,
},
expectedErr: "-dev-connect is only supported on linux",
},
{
devConfig: &devModeConfig{
defaultMode: true,
connectMode: true,
},
expectedErr: "-dev-connect is only supported on linux",
},
}
}
if runtime.GOOS == "linux" {
testutil.RequireRoot(t)
cases = []struct {
dev bool
connect bool
expected *devModeConfig
devConfig *devModeConfig
expectedErr string
}{
{false, false, nil, ""},
{true, false, &devModeConfig{defaultMode: true, connectMode: false}, ""},
{true, true, &devModeConfig{defaultMode: true, connectMode: true}, ""},
{false, true, &devModeConfig{defaultMode: false, connectMode: true}, ""},
{
devConfig: &devModeConfig{
connectMode: true,
},
expectedErr: "",
},
{
devConfig: &devModeConfig{
defaultMode: true,
connectMode: true,
},
expectedErr: "",
},
}
}
for _, c := range cases {
t.Run("", func(t *testing.T) {
mode, err := newDevModeConfig(c.dev, c.connect)
if err != nil && c.expectedErr == "" {
t.Fatalf("unexpected error: %v", err)
}
if err != nil && !strings.Contains(err.Error(), c.expectedErr) {
t.Fatalf("expected %s; got %v", c.expectedErr, err)
}
if mode == nil && c.expected != nil {
t.Fatalf("expected %+v but got nil", c.expected)
}
if mode != nil {
if c.expected.defaultMode != mode.defaultMode ||
c.expected.connectMode != mode.connectMode {
t.Fatalf("expected %+v, got %+v", c.expected, mode)
}
err := c.devConfig.validate()
if c.expectedErr != "" {
must.Error(t, err)
} else {
must.NoError(t, err)
}
})
}

View File

@@ -98,8 +98,16 @@ via CLI arguments. The `agent` command accepts the following arguments:
but you may pass an optional comma-separated list of mode configurations:
- `-dev-connect`: Start the agent in development mode, but bind to a public
network interface rather than localhost for using Consul Connect. This mode
is supported only on Linux as root.
network interface rather than localhost for using Consul Connect. It may be
used with `-dev-consul` to configure default workload identities for Consul.
This mode is supported only on Linux as root.
- `-dev-consul`: Starts the agent in development mode with a default Consul
configuration for Nomad workload identity. It may be used with `-dev-connect`
to configure the agent for Consul Service Mesh.
- `-dev-vault`: Starts the agent in development mode with a default Vault
configuration for Nomad workload identity.
- `-encrypt`: Set the Serf encryption key. See the [Encryption Overview] for
more details.