Skip to content

Commit

Permalink
Support Nomad Variables (#1632)
Browse files Browse the repository at this point in the history
Nomad SV: Add Test helpers
Nomad SV: Secure Variables List dependency
Nomad SV: Secure Variables Get dependency
Nomad SV: Add template functions

Add test for variable value leak via error
Add variable values to redactinator

Support Namespace and Region for SV
Upgrading Nomad API version to v1.4.1
Fix copypasta in Nomad configuration
  • Loading branch information
angrycub authored Oct 26, 2022
1 parent c09cfad commit 90370e0
Show file tree
Hide file tree
Showing 19 changed files with 1,753 additions and 55 deletions.
19 changes: 19 additions & 0 deletions config/nomad_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"fmt"
"net"
"os"
"reflect"
"strings"
"testing"
"time"
)
Expand Down Expand Up @@ -193,6 +195,23 @@ func TestNomadConfig_Merge(t *testing.T) {
}

func TestNomadConfig_Finalize(t *testing.T) {
// This test is envvar sensitive, so there are a whole lot of them that need
// to be backed up and reset after the test
nomadVars := make(map[string]string)
for _, v := range os.Environ() {
if strings.HasPrefix(v, "NOMAD_") {
k, v, found := strings.Cut(v, "=")
if found {
nomadVars[k] = v
os.Unsetenv(k)
}
}
}
t.Cleanup(func() {
for k, v := range nomadVars {
os.Setenv(k, v)
}
})
cases := []struct {
name string
i *NomadConfig
Expand Down
2 changes: 1 addition & 1 deletion dependency/client_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func (c *ClientSet) CreateNomadClient(i *CreateNomadClientInput) error {
}
}

// This transport will attempt to keep connections open to the Vault server.
// This transport will attempt to keep connections open to the Nomad server.
var dialer TransportDialer
dialer = &net.Dialer{
Timeout: i.TransportDialTimeout,
Expand Down
23 changes: 14 additions & 9 deletions dependency/dependency.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@ import (
)

const (
dcRe = `(@(?P<dc>[[:word:]\.\-\_]+))?`
keyRe = `/?(?P<key>[^@]+)`
filterRe = `(\|(?P<filter>[[:word:]\,]+))?`
serviceNameRe = `(?P<name>[[:word:]\-\_]+)`
nodeNameRe = `(?P<name>[[:word:]\.\-\_]+)`
nearRe = `(~(?P<near>[[:word:]\.\-\_]+))?`
prefixRe = `/?(?P<prefix>[^@]+)`
tagRe = `((?P<tag>[[:word:]=:\.\-\_]+)\.)?`
regionRe = `(@(?P<region>[[:word:]\.\-\_]+))?`
dcRe = `(@(?P<dc>[[:word:]\.\-\_]+))?`
keyRe = `/?(?P<key>[^@]+)`
filterRe = `(\|(?P<filter>[[:word:]\,]+))?`
serviceNameRe = `(?P<name>[[:word:]\-\_]+)`
nodeNameRe = `(?P<name>[[:word:]\.\-\_]+)`
nearRe = `(~(?P<near>[[:word:]\.\-\_]+))?`
prefixRe = `/?(?P<prefix>[^@]+)`
tagRe = `((?P<tag>[[:word:]=:\.\-\_]+)\.)?`
regionRe = `(@(?P<region>[[:word:]\.\-\_]+))?`
nvPathRe = `/?(?P<path>[^@]+)`
nvNamespaceRe = `(@(?P<namespace>[[:word:]\-\_]+))?`
nvListPrefixRe = `/?(?P<prefix>[^@]*)`
nvListNSRe = `(@(?P<namespace>([[:word:]\-\_]+|\*)))?`
nvRegionRe = `(\.(?P<region>[[:word:]\-\_]+))?`
)

type Type int
Expand Down
28 changes: 28 additions & 0 deletions dependency/dependency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,31 @@ func Fatalf(format string, args ...interface{}) {
fmt.Printf(format, args...)
runtime.Goexit()
}

func (v *nomadServer) CreateVariable(path string, data map[string]string, opts *nomadapi.WriteOptions) error {
nVar := nomadapi.NewVariable(path)
for k, v := range data {
nVar.Items[k] = v
}
_, _, err := testClients.Nomad().Variables().Update(nVar, opts)
if err != nil {
fmt.Println(err)
}
return err
}
func (v *nomadServer) CreateNamespace(name string, opts *nomadapi.WriteOptions) error {
ns := nomadapi.Namespace{Name: name}
_, err := testClients.Nomad().Namespaces().Register(&ns, opts)
if err != nil {
fmt.Println(err)
}
return err
}

func (v *nomadServer) DeleteVariable(path string, opts *nomadapi.WriteOptions) error {
_, err := testClients.Nomad().Variables().Delete(path, opts)
if err != nil {
fmt.Println(err)
}
return err
}
133 changes: 133 additions & 0 deletions dependency/nomad_var_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package dependency

import (
"fmt"
"log"
"net/url"
"regexp"
"strings"

"github.com/pkg/errors"
)

var (
// Ensure implements
_ Dependency = (*NVGetQuery)(nil)

// NVGetQueryRe is the regular expression to use.
NVGetQueryRe = regexp.MustCompile(`\A` + nvPathRe + nvNamespaceRe + nvRegionRe + `\z`)
)

// NVGetQuery queries the KV store for a single key.
type NVGetQuery struct {
stopCh chan struct{}

path string
namespace string
region string

blockOnNil bool
}

// NewNVGetQuery parses a string into a dependency.
func NewNVGetQuery(ns, s string) (*NVGetQuery, error) {
s = strings.TrimSpace(s)
s = strings.Trim(s, "/")

if s != "" && !NVGetQueryRe.MatchString(s) {
return nil, fmt.Errorf("nomad.var.get: invalid format: %q", s)
}

m := regexpMatch(NVGetQueryRe, s)
out := &NVGetQuery{
stopCh: make(chan struct{}, 1),
path: m["path"],
namespace: m["namespace"],
region: m["region"],
}
if out.namespace == "" && ns != "" {
out.namespace = ns
}
return out, nil
}

// Fetch queries the Nomad API defined by the given client.
func (d *NVGetQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) {
select {
case <-d.stopCh:
return nil, nil, ErrStopped
default:
}

opts = opts.Merge(&QueryOptions{})

log.Printf("[TRACE] %s: GET %s", d, &url.URL{
Path: "/v1/var/" + d.path,
RawQuery: opts.String(),
})

nOpts := opts.ToNomadOpts()
nOpts.Namespace = d.namespace
nOpts.Region = d.region
// NOTE: The Peek method of the Nomad Variables API will check a value,
// return it if it exists, but return a nil value and NO error if it is
// not found.
nVar, qm, err := clients.Nomad().Variables().Peek(d.path, nOpts)
if err != nil {
return nil, nil, errors.Wrap(err, d.String())
}

rm := &ResponseMetadata{
LastIndex: qm.LastIndex,
LastContact: qm.LastContact,
BlockOnNil: d.blockOnNil,
}

if nVar == nil {
log.Printf("[TRACE] %s: returned nil", d)
return nil, rm, nil
}

items := &NewNomadVariable(nVar).Items
log.Printf("[TRACE] %s: returned %q", d, nVar.Path)
return items, rm, nil
}

// EnableBlocking turns this into a blocking KV query.
func (d *NVGetQuery) EnableBlocking() {
d.blockOnNil = true
}

// CanShare returns a boolean if this dependency is shareable.
func (d *NVGetQuery) CanShare() bool {
return true
}

// String returns the human-friendly version of this dependency.
// This value is also used to disambiguate multiple instances in the Brain
func (d *NVGetQuery) String() string {
ns := d.namespace
if ns == "" {
ns = "default"
}
region := d.region
if region == "" {
region = "global"
}
path := d.path
key := fmt.Sprintf("%s@%s.%s", path, ns, region)
if d.blockOnNil {
return fmt.Sprintf("nomad.var.block(%s)", key)
}
return fmt.Sprintf("nomad.var.get(%s)", key)
}

// Stop halts the dependency's fetch function.
func (d *NVGetQuery) Stop() {
close(d.stopCh)
}

// Type returns the type of this dependency.
func (d *NVGetQuery) Type() Type {
return TypeNomad
}
Loading

0 comments on commit 90370e0

Please sign in to comment.