From 4484d361eeff16fe3002781c03aabf230e9d5e17 Mon Sep 17 00:00:00 2001 From: Drew Bailey Date: Tue, 23 Mar 2021 09:08:14 -0400 Subject: [PATCH] configuration and oss components for licensing (#10216) * configuration and oss components for licensing * vendor sync --- api/operator.go | 16 +++++++- command/agent/agent.go | 4 ++ command/agent/command.go | 6 +++ command/agent/config.go | 10 +++++ command/agent/config_parse_test.go | 1 + command/agent/testdata/basic.hcl | 2 + command/agent/testdata/basic.json | 3 +- command/license.go | 16 ++++---- command/license_put.go | 40 ++++++++++++++++++- nomad/config.go | 3 ++ nomad/server.go | 4 ++ nomad/server_setup_oss.go | 4 ++ .../hashicorp/nomad/api/operator.go | 16 +++++++- website/content/api-docs/operator/license.mdx | 10 ++++- website/content/docs/commands/license/put.mdx | 7 +++- website/content/docs/configuration/server.mdx | 6 +++ 16 files changed, 135 insertions(+), 13 deletions(-) diff --git a/api/operator.go b/api/operator.go index a4d8c56c2..3c4ae1e2d 100644 --- a/api/operator.go +++ b/api/operator.go @@ -274,15 +274,29 @@ type License struct { } type LicenseReply struct { - License *License + License *License + ConfigOutdated bool QueryMeta } +type ApplyLicenseOptions struct { + Force bool +} + func (op *Operator) LicensePut(license string, q *WriteOptions) (*WriteMeta, error) { + return op.ApplyLicense(license, nil, q) +} + +func (op *Operator) ApplyLicense(license string, opts *ApplyLicenseOptions, q *WriteOptions) (*WriteMeta, error) { r, err := op.c.newRequest("PUT", "/v1/operator/license") if err != nil { return nil, err } + + if opts != nil && opts.Force { + r.params.Add("force", "true") + } + r.setWriteOptions(q) r.body = strings.NewReader(license) diff --git a/command/agent/agent.go b/command/agent/agent.go index 5207451bb..b98ce1ce7 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -418,6 +418,10 @@ func convertServerConfig(agentConfig *Config) (*nomad.Config, error) { conf.RPCMaxConnsPerClient = limit } + // Add Enterprise license configs + conf.LicenseEnv = agentConfig.Server.LicenseEnv + conf.LicensePath = agentConfig.Server.LicensePath + return conf, nil } diff --git a/command/agent/command.go b/command/agent/command.go index cf875d522..0346a726c 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -285,6 +285,12 @@ func (c *Command) readConfig() *Config { config.PluginDir = filepath.Join(config.DataDir, "plugins") } + // License configuration options + config.Server.LicenseEnv = os.Getenv("NOMAD_LICENSE") + if config.Server.LicensePath == "" { + config.Server.LicensePath = os.Getenv("NOMAD_LICENSE_PATH") + } + config.Server.DefaultSchedulerConfig.Canonicalize() if !c.isValidConfig(config, cmdConfig) { diff --git a/command/agent/config.go b/command/agent/config.go index b9d108e2f..5f3311f7f 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -494,6 +494,13 @@ type ServerConfig struct { // for the EventBufferSize is 1. EventBufferSize *int `hcl:"event_buffer_size"` + // LicensePath is the path to search for an enterprise license. + LicensePath string `hcl:"license_path"` + + // LicenseEnv is the full enterprise license. If NOMAD_LICENSE + // is set, LicenseEnv will be set to the value at startup. + LicenseEnv string + // ExtraKeysHCL is used by hcl to surface unexpected keys ExtraKeysHCL []string `hcl:",unusedKeys" json:"-"` } @@ -1410,6 +1417,9 @@ func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig { if b.ServerJoin != nil { result.ServerJoin = result.ServerJoin.Merge(b.ServerJoin) } + if b.LicensePath != "" { + result.LicensePath = b.LicensePath + } if b.EnableEventBroker != nil { result.EnableEventBroker = b.EnableEventBroker diff --git a/command/agent/config_parse_test.go b/command/agent/config_parse_test.go index b3c5ab87d..9f6d66a69 100644 --- a/command/agent/config_parse_test.go +++ b/command/agent/config_parse_test.go @@ -138,6 +138,7 @@ var basicConfig = &Config{ ServiceSchedulerEnabled: true, }, }, + LicensePath: "/tmp/nomad.hclic", }, ACL: &ACLConfig{ Enabled: true, diff --git a/command/agent/testdata/basic.hcl b/command/agent/testdata/basic.hcl index 74b33e85a..b4a55197d 100644 --- a/command/agent/testdata/basic.hcl +++ b/command/agent/testdata/basic.hcl @@ -148,6 +148,8 @@ server { service_scheduler_enabled = true } } + + license_path = "/tmp/nomad.hclic" } acl { diff --git a/command/agent/testdata/basic.json b/command/agent/testdata/basic.json index 8a988b309..02de2490c 100644 --- a/command/agent/testdata/basic.json +++ b/command/agent/testdata/basic.json @@ -309,7 +309,8 @@ "service_scheduler_enabled": true }] }], - "upgrade_version": "0.8.0" + "upgrade_version": "0.8.0", + "license_path": "/tmp/nomad.hclic" } ], "syslog_facility": "LOCAL1", diff --git a/command/license.go b/command/license.go index ddb58c47c..a6a55c49b 100644 --- a/command/license.go +++ b/command/license.go @@ -48,19 +48,16 @@ func (l *LicenseCommand) Run(args []string) int { } func OutputLicenseReply(ui cli.Ui, resp *api.LicenseReply) int { - var validity string now := time.Now() - if resp.License.ExpirationTime.Before(now) { - validity = "expired!" - outputLicenseInfo(ui, resp.License, true, validity) + expired := resp.License.ExpirationTime.Before(now) + outputLicenseInfo(ui, resp.License, expired) + if expired { return 1 } - validity = "valid" - outputLicenseInfo(ui, resp.License, false, validity) return 0 } -func outputLicenseInfo(ui cli.Ui, lic *api.License, expired bool, validity string) { +func outputLicenseInfo(ui cli.Ui, lic *api.License, expired bool) { expStr := "" if expired { expStr = fmt.Sprintf("Expired At|%s", lic.ExpirationTime.String()) @@ -68,11 +65,16 @@ func outputLicenseInfo(ui cli.Ui, lic *api.License, expired bool, validity strin expStr = fmt.Sprintf("Expires At|%s", lic.ExpirationTime.String()) } + validity := "valid" + if expired { + validity = "expired!" + } output := []string{ fmt.Sprintf("Product|%s", lic.Product), fmt.Sprintf("License Status|%s", validity), fmt.Sprintf("License ID|%s", lic.LicenseID), fmt.Sprintf("Customer ID|%s", lic.CustomerID), + fmt.Sprintf("Issued At|%s", lic.IssueTime), expStr, fmt.Sprintf("Terminates At|%s", lic.TerminationTime.String()), fmt.Sprintf("Datacenter|%s", lic.InstallationID), diff --git a/command/license_put.go b/command/license_put.go index 942c57fdb..d65740b0a 100644 --- a/command/license_put.go +++ b/command/license_put.go @@ -8,7 +8,9 @@ import ( "os" "strings" + "github.com/hashicorp/nomad/api" "github.com/pkg/errors" + "github.com/posener/complete" ) type LicensePutCommand struct { @@ -26,10 +28,20 @@ Usage: nomad license put [options] When ACLs are enabled, this command requires a token with the 'operator:write' capability. + Use the -force flag to override the currently installed license with an older + license. + General Options: ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + ` +License Options: + + -force + Force is used to override the currently installed license. By default + Nomad will keep the newest license, as determined by the license issue + date. Use this flag to apply an older license. + Install a new license from a file: $ nomad license put @@ -46,11 +58,20 @@ func (c *LicensePutCommand) Synopsis() string { return "Install a new Nomad Enterprise License" } +func (c *LicensePutCommand) AutoCompleteFlags() complete.Flags { + return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), + complete.Flags{ + "-force": complete.PredictNothing, + }) +} + func (c *LicensePutCommand) Name() string { return "license put" } func (c *LicensePutCommand) Run(args []string) int { + var force bool flags := c.Meta.FlagSet(c.Name(), FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } + flags.BoolVar(&force, "force", false, "") if err := flags.Parse(args); err != nil { c.Ui.Error(fmt.Sprintf("Error parsing flags: %s", err)) @@ -70,12 +91,29 @@ func (c *LicensePutCommand) Run(args []string) int { return 1 } - _, err = client.Operator().LicensePut(data, nil) + opts := &api.ApplyLicenseOptions{ + Force: force, + } + _, err = client.Operator().ApplyLicense(data, opts, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error putting license: %v", err)) return 1 } + lic, _, err := client.Operator().LicenseGet(nil) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error retrieving new license: %v", err)) + return 1 + } + + if lic.ConfigOutdated { + c.Ui.Warn(` +WARNING: The server's configured file license is now outdated. Please update or +remove the server's license configuration to prevent initialization issues with +potentially expired licenses. +`) // New line for cli output + } + c.Ui.Output("Successfully applied license") return 0 } diff --git a/nomad/config.go b/nomad/config.go index 76663c25d..049476827 100644 --- a/nomad/config.go +++ b/nomad/config.go @@ -355,6 +355,9 @@ type Config struct { // LicenseConfig is a tunable knob for enterprise license testing. LicenseConfig *LicenseConfig + LicenseEnv string + LicensePath string + // AgentShutdown is used to call agent.Shutdown from the context of a Server // It is used primarily for licensing AgentShutdown func() error diff --git a/nomad/server.go b/nomad/server.go index 5fabdc385..4c55b3c8f 100644 --- a/nomad/server.go +++ b/nomad/server.go @@ -805,6 +805,10 @@ func (s *Server) Reload(newConfig *Config) error { } } + if newConfig.LicenseEnv != "" || newConfig.LicensePath != "" { + s.EnterpriseState.ReloadLicense(newConfig) + } + return mErr.ErrorOrNil() } diff --git a/nomad/server_setup_oss.go b/nomad/server_setup_oss.go index f5ca226cb..f85158daf 100644 --- a/nomad/server_setup_oss.go +++ b/nomad/server_setup_oss.go @@ -16,6 +16,10 @@ func (es *EnterpriseState) Features() uint64 { return 0 } +func (es *EnterpriseState) ReloadLicense(_ *Config) error { + return nil +} + func (s *Server) setupEnterprise(config *Config) error { // Set up the OSS version of autopilot apDelegate := &AutopilotDelegate{s} diff --git a/vendor/github.com/hashicorp/nomad/api/operator.go b/vendor/github.com/hashicorp/nomad/api/operator.go index a4d8c56c2..3c4ae1e2d 100644 --- a/vendor/github.com/hashicorp/nomad/api/operator.go +++ b/vendor/github.com/hashicorp/nomad/api/operator.go @@ -274,15 +274,29 @@ type License struct { } type LicenseReply struct { - License *License + License *License + ConfigOutdated bool QueryMeta } +type ApplyLicenseOptions struct { + Force bool +} + func (op *Operator) LicensePut(license string, q *WriteOptions) (*WriteMeta, error) { + return op.ApplyLicense(license, nil, q) +} + +func (op *Operator) ApplyLicense(license string, opts *ApplyLicenseOptions, q *WriteOptions) (*WriteMeta, error) { r, err := op.c.newRequest("PUT", "/v1/operator/license") if err != nil { return nil, err } + + if opts != nil && opts.Force { + r.params.Add("force", "true") + } + r.setWriteOptions(q) r.body = strings.NewReader(license) diff --git a/website/content/api-docs/operator/license.mdx b/website/content/api-docs/operator/license.mdx index dcc554d50..090d2dea0 100644 --- a/website/content/api-docs/operator/license.mdx +++ b/website/content/api-docs/operator/license.mdx @@ -84,6 +84,14 @@ The table below shows this endpoint's support for | ---------------- | ---------------- | | `NO` | `operator:write` | +### Parameters + +- `force` `(bool: false)` Force the license to be applied. By default, Nomad + will only accept a new license if it is newer than the one currently applied + (specified by the license issue date). Use `force` to override and apply an + older, unexpired license. + + ### Sample Payload The payload is the raw license blob. @@ -94,7 +102,7 @@ The payload is the raw license blob. $ curl \ --request PUT \ --data @nomad.license \ - https://localhost:4646/v1/operator/license + https://localhost:4646/v1/operator/license?force=true ``` ### Sample Response diff --git a/website/content/docs/commands/license/put.mdx b/website/content/docs/commands/license/put.mdx index 77e9e74ca..1c8635242 100644 --- a/website/content/docs/commands/license/put.mdx +++ b/website/content/docs/commands/license/put.mdx @@ -28,6 +28,11 @@ capability. ## Put Options +- `-force`: Force the license to be applied. By default, Nomad will only accept + a new license if it is newer than the one currently applied (specified by the + license issue date). Use `-force` to override and apply an older, unexpired + license. + ```plaintext Install a new license from a file: @@ -43,7 +48,7 @@ Install a new license from stdin: Set a license using a path ```shell-session -$ nomad license put /path/to/my/license.txt +$ nomad license put -force /path/to/my/license.hclic ``` Set a license using a path diff --git a/website/content/docs/configuration/server.mdx b/website/content/docs/configuration/server.mdx index 3248cb8b1..d8a0f1fcc 100644 --- a/website/content/docs/configuration/server.mdx +++ b/website/content/docs/configuration/server.mdx @@ -120,6 +120,12 @@ server { processing delays as well as clock skew. This is specified using a label suffix like "30s" or "1h". +- `license_path` `(string: "")` - Specifies the path to load a Nomad Enterprise + license from. This must be an absolute path (`/opt/nomad/license.hclic`). The + license can also be set by setting `NOMAD_LICENSE_PATH` or by setting + `NOMAD_LICENSE` as the entire license value. `license_path` has the highest + precedence, followed by `NOMAD_LICENSE` and then `NOMAD_LICENSE_PATH`. + - `min_heartbeat_ttl` `(string: "10s")` - Specifies the minimum time between node heartbeats. This is used as a floor to prevent excessive updates. This is specified using a label suffix like "30s" or "1h". Lowering the minimum TTL is