Provide mock secure variables implementation (#12980)

* Add SecureVariable mock
* Add SecureVariableStub
* Add SecureVariable Copy and Stub funcs
This commit is contained in:
Charlie Voiselle
2022-05-13 10:11:27 -07:00
committed by Tim Gross
parent 9b1bea1bc1
commit 15d6dde25c
12 changed files with 661 additions and 13 deletions

View File

@@ -57,13 +57,16 @@ var (
// tag isn't enabled
stubHTML = "<html><p>Nomad UI is disabled</p></html>"
// allowCORS sets permissive CORS headers for a handler
allowCORS = cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"HEAD", "GET"},
AllowedHeaders: []string{"*"},
AllowCredentials: true,
})
// allowCORSWithMethods sets permissive CORS headers for a handler, used by
// wrapCORS and wrapCORSWithMethods
allowCORSWithMethods = func(methods ...string) *cors.Cors {
return cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: methods,
AllowedHeaders: []string{"*"},
AllowCredentials: true,
})
}
)
type handlerFn func(resp http.ResponseWriter, req *http.Request) (interface{}, error)
@@ -407,10 +410,14 @@ func (s HTTPServer) registerHandlers(enableDebug bool) {
s.mux.HandleFunc("/v1/operator/scheduler/configuration", s.wrap(s.OperatorSchedulerConfiguration))
s.mux.HandleFunc("/v1/event/stream", s.wrap(s.EventStream))
s.mux.HandleFunc("/v1/namespaces", s.wrap(s.NamespacesRequest))
s.mux.HandleFunc("/v1/namespace", s.wrap(s.NamespaceCreateRequest))
s.mux.HandleFunc("/v1/namespace/", s.wrap(s.NamespaceSpecificRequest))
s.mux.Handle("/v1/vars", wrapCORS(s.wrap(s.SecureVariablesListRequest)))
s.mux.Handle("/v1/var/", wrapCORSWithAllowedMethods(s.wrap(s.SecureVariableSpecificRequest), "HEAD", "GET", "PUT", "DELETE"))
uiConfigEnabled := s.agent.config.UI != nil && s.agent.config.UI.Enabled
if uiEnabled && uiConfigEnabled {
@@ -901,7 +908,14 @@ func (s *HTTPServer) wrapUntrustedContent(handler handlerFn) handlerFn {
}
}
// wrapCORS wraps a HandlerFunc in allowCORS and returns a http.Handler
// wrapCORS wraps a HandlerFunc in allowCORS with read ("HEAD", "GET") methods
// and returns a http.Handler
func wrapCORS(f func(http.ResponseWriter, *http.Request)) http.Handler {
return allowCORS.Handler(http.HandlerFunc(f))
return wrapCORSWithAllowedMethods(f, "HEAD", "GET")
}
// wrapCORSWithAllowedMethods wraps a HandlerFunc in an allowCORS with the given
// method list and returns a http.Handler
func wrapCORSWithAllowedMethods(f func(http.ResponseWriter, *http.Request), methods ...string) http.Handler {
return allowCORSWithMethods(methods...).Handler(http.HandlerFunc(f))
}

View File

@@ -0,0 +1,109 @@
package agent
import (
"net/http"
"strings"
"github.com/hashicorp/nomad/nomad/structs"
)
func (s *HTTPServer) SecureVariablesListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
if req.Method != "GET" {
return nil, CodedError(405, ErrInvalidMethod)
}
args := structs.SecureVariablesListRequest{}
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
return nil, nil
}
var out structs.SecureVariablesListResponse
if err := s.agent.RPC("SecureVariables.List", &args, &out); err != nil {
return nil, err
}
setMeta(resp, &out.QueryMeta)
if out.Data == nil {
out.Data = make([]*structs.SecureVariableStub, 0)
}
return out.Data, nil
}
func (s *HTTPServer) SecureVariableSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
path := strings.TrimPrefix(req.URL.Path, "/v1/var/")
if len(path) == 0 {
return nil, CodedError(http.StatusBadRequest, "Missing secure variable path")
}
switch req.Method {
case http.MethodGet:
return s.secureVariableQuery(resp, req, path)
case http.MethodPut, http.MethodPost:
return s.secureVariableUpsert(resp, req, path)
case http.MethodDelete:
return s.secureVariableDelete(resp, req, path)
default:
return nil, CodedError(http.StatusBadRequest, ErrInvalidMethod)
}
}
func (s *HTTPServer) secureVariableQuery(resp http.ResponseWriter, req *http.Request,
path string) (interface{}, error) {
args := structs.SecureVariablesReadRequest{
Path: path,
}
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
return nil, nil
}
var out structs.SecureVariablesReadResponse
if err := s.agent.RPC("SecureVariables.Read", &args, &out); err != nil {
return nil, err
}
setMeta(resp, &out.QueryMeta)
if out.Data == nil {
return nil, CodedError(404, "Secure variable not found")
}
return out.Data, nil
}
func (s *HTTPServer) secureVariableUpsert(resp http.ResponseWriter, req *http.Request,
path string) (interface{}, error) {
// Parse the SecureVariable
var SecureVariable structs.SecureVariable
if err := decodeBody(req, &SecureVariable); err != nil {
return nil, CodedError(http.StatusBadRequest, err.Error())
}
SecureVariable.Path = path
// Format the request
args := structs.SecureVariablesUpsertRequest{
Data: &SecureVariable,
}
s.parseWriteRequest(req, &args.WriteRequest)
var out structs.SecureVariablesUpsertResponse
if err := s.agent.RPC("SecureVariables.Update", &args, &out); err != nil {
return nil, err
}
setIndex(resp, out.WriteMeta.Index)
return nil, nil
}
func (s *HTTPServer) secureVariableDelete(resp http.ResponseWriter, req *http.Request,
path string) (interface{}, error) {
args := structs.SecureVariablesDeleteRequest{
Path: path,
}
s.parseWriteRequest(req, &args.WriteRequest)
var out structs.SecureVariablesDeleteResponse
if err := s.agent.RPC("SecureVariables.Delete", &args, &out); err != nil {
return nil, err
}
setIndex(resp, out.WriteMeta.Index)
return nil, nil
}

View File

@@ -0,0 +1,254 @@
package agent
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/require"
)
var (
cb = func(c *Config) {
var ns int
ns = 0
c.LogLevel = "ERROR"
c.Server.NumSchedulers = &ns
}
)
func TestHTTP_SecureVariableList(t *testing.T) {
//ci.Parallel(t)
httpTest(t, cb, func(s *TestAgent) {
// Test the empty list case
req, err := http.NewRequest("GET", "/v1/vars", nil)
require.NoError(t, err)
respW := httptest.NewRecorder()
// Make the request
obj, err := s.Server.SecureVariablesListRequest(respW, req)
require.NoError(t, err)
// add vars and test a populated backend
sv1 := mock.SecureVariable()
sv2 := mock.SecureVariable()
sv3 := mock.SecureVariable()
sv4 := mock.SecureVariable()
sv4.Path = sv1.Path + "/child"
for _, sv := range []*structs.SecureVariable{sv1, sv2, sv3, sv4} {
require.NoError(t, rpcWriteSV(s, sv))
}
// Make the HTTP request
req, err = http.NewRequest("GET", "/v1/vars", nil)
require.NoError(t, err)
respW = httptest.NewRecorder()
// Make the request
obj, err = s.Server.SecureVariablesListRequest(respW, req)
require.NoError(t, err)
// Check for the index
require.NotZero(t, respW.HeaderMap.Get("X-Nomad-Index"))
require.Equal(t, "true", respW.HeaderMap.Get("X-Nomad-KnownLeader"))
require.NotZero(t, respW.HeaderMap.Get("X-Nomad-LastContact"))
// Check the output (the 3 we register )
require.Len(t, obj.([]*structs.SecureVariableStub), 4)
// test prefix query
req, err = http.NewRequest("GET", "/v1/vars?prefix="+sv1.Path, nil)
require.NoError(t, err)
respW = httptest.NewRecorder()
// Make the request
obj, err = s.Server.SecureVariablesListRequest(respW, req)
require.NoError(t, err)
require.Len(t, obj.([]*structs.SecureVariableStub), 2)
})
}
func TestHTTP_SecureVariableQuery(t *testing.T) {
//ci.Parallel(t)
httpTest(t, cb, func(s *TestAgent) {
// Make a request for a non-existent variable
req, err := http.NewRequest("GET", "/v1/var/does/not/exist", nil)
require.NoError(t, err)
respW := httptest.NewRecorder()
obj, err := s.Server.SecureVariableSpecificRequest(respW, req)
require.EqualError(t, err, "Secure variable not found")
// Don't pass a path
req, err = http.NewRequest("GET", "/v1/var/", nil)
require.NoError(t, err)
respW = httptest.NewRecorder()
obj, err = s.Server.SecureVariableSpecificRequest(respW, req)
require.EqualError(t, err, "Missing secure variable path")
// Use an incorrect verb
req, err = http.NewRequest("LOLWUT", "/v1/var/foo", nil)
require.NoError(t, err)
respW = httptest.NewRecorder()
obj, err = s.Server.SecureVariableSpecificRequest(respW, req)
require.EqualError(t, err, ErrInvalidMethod)
// Use RPC to make a test variable
sv1 := mock.SecureVariable()
require.NoError(t, rpcWriteSV(s, sv1))
// Query a variable
req, err = http.NewRequest("GET", "/v1/var/"+sv1.Path, nil)
require.NoError(t, err)
respW = httptest.NewRecorder()
// Make the request
obj, err = s.Server.SecureVariableSpecificRequest(respW, req)
require.NoError(t, err)
// Check for the index
require.NotZero(t, respW.HeaderMap.Get("X-Nomad-Index"))
require.Equal(t, "true", respW.HeaderMap.Get("X-Nomad-KnownLeader"))
require.NotZero(t, respW.HeaderMap.Get("X-Nomad-LastContact"))
// Check the output
require.Equal(t, sv1.Path, obj.(*structs.SecureVariable).Path)
})
}
func TestHTTP_SecureVariableCreate(t *testing.T) {
//ci.Parallel(t)
httpTest(t, cb, func(s *TestAgent) {
sv1 := mock.SecureVariable()
require.NoError(t, rpcWriteSV(s, sv1))
// Make a change for update
sv1U := sv1.Copy()
sv1U.UnencryptedData["newness"] = "awwyeah"
// Make the HTTP request
buf := encodeReq(&sv1U)
req, err := http.NewRequest("PUT", "/v1/var/"+sv1.Path, buf)
require.NoError(t, err)
respW := httptest.NewRecorder()
// Make the request
obj, err := s.Server.SecureVariableSpecificRequest(respW, req)
require.NoError(t, err)
require.Nil(t, obj)
// Check for the index
require.NotZero(t, respW.HeaderMap.Get("X-Nomad-Index"))
// Check the variable was created
out, err := rpcReadSV(s, sv1.Path)
require.NoError(t, err)
require.NotNil(t, out)
sv1.CreateIndex, sv1.ModifyIndex = out.CreateIndex, out.ModifyIndex
require.Equal(t, sv1.Path, out.Path)
require.NotEqual(t, sv1, out)
require.Contains(t, out.UnencryptedData, "newness")
// break the request body
badBuf := encodeBrokenReq(&sv1U)
req, err = http.NewRequest("PUT", "/v1/var/"+sv1.Path, badBuf)
require.NoError(t, err)
respW = httptest.NewRecorder()
// Make the request
obj, err = s.Server.SecureVariableSpecificRequest(respW, req)
require.EqualError(t, err, "unexpected EOF")
var cErr HTTPCodedError
require.ErrorAs(t, err, &cErr)
require.Equal(t, http.StatusBadRequest, cErr.Code())
})
}
func TestHTTP_SecureVariableUpdate(t *testing.T) {
//ci.Parallel(t)
httpTest(t, cb, func(s *TestAgent) {
// Make the HTTP request
sv1 := mock.SecureVariable()
buf := encodeReq(sv1)
req, err := http.NewRequest("PUT", "/v1/var/"+sv1.Path, buf)
require.NoError(t, err)
respW := httptest.NewRecorder()
// Make the request
obj, err := s.Server.SecureVariableSpecificRequest(respW, req)
require.NoError(t, err)
require.Nil(t, obj)
// Check for the index
require.NotZero(t, respW.HeaderMap.Get("X-Nomad-Index"))
// Check the variable was updated
out, err := rpcReadSV(s, sv1.Path)
require.NoError(t, err)
require.NotNil(t, out)
sv1.CreateIndex, sv1.ModifyIndex = out.CreateIndex, out.ModifyIndex
require.Equal(t, sv1.Path, out.Path)
require.Equal(t, sv1, out)
})
}
func TestHTTP_SecureVariableDelete(t *testing.T) {
//ci.Parallel(t)
httpTest(t, cb, func(s *TestAgent) {
sv1 := mock.SecureVariable()
require.NoError(t, rpcWriteSV(s, sv1))
// Make the HTTP request
req, err := http.NewRequest("DELETE", "/v1/var/"+sv1.Path, nil)
require.NoError(t, err)
respW := httptest.NewRecorder()
// Make the request
obj, err := s.Server.SecureVariableSpecificRequest(respW, req)
require.NoError(t, err)
require.Nil(t, obj)
// Check for the index
require.NotZero(t, respW.HeaderMap.Get("X-Nomad-Index"))
// Check variable bag was deleted
sv, err := rpcReadSV(s, sv1.Path)
require.NoError(t, err)
require.Nil(t, sv)
})
}
func encodeBrokenReq(obj interface{}) io.ReadCloser {
// var buf *bytes.Buffer
// enc := json.NewEncoder(buf)
// enc.Encode(obj)
b, _ := json.Marshal(obj)
b = b[0 : len(b)-5] // strip newline and final }
return ioutil.NopCloser(bytes.NewReader(b))
}
func rpcReadSV(s *TestAgent, p string) (*structs.SecureVariable, error) {
checkArgs := structs.SecureVariablesReadRequest{Path: p, QueryOptions: structs.QueryOptions{Region: "global"}}
var checkResp structs.SecureVariablesReadResponse
err := s.Agent.RPC("SecureVariables.Read", &checkArgs, &checkResp)
return checkResp.Data, err
}
func rpcWriteSV(s *TestAgent, sv *structs.SecureVariable) error {
args := structs.SecureVariablesUpsertRequest{
Data: sv,
WriteRequest: structs.WriteRequest{Region: "global"},
}
var resp structs.SecureVariablesUpsertResponse
return s.Agent.RPC("SecureVariables.Create", &args, &resp)
}

1
go.mod
View File

@@ -162,6 +162,7 @@ require (
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/bmatcuk/doublestar v1.1.5 // indirect
github.com/boltdb/bolt v1.3.1 // indirect
github.com/brianvoe/gofakeit/v6 v6.16.0
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect

2
go.sum
View File

@@ -190,6 +190,8 @@ github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/brianvoe/gofakeit/v6 v6.16.0 h1:EelCqtfArd8ppJ0z+TpOxXH8sVWNPBadPNdCDSMMw7k=
github.com/brianvoe/gofakeit/v6 v6.16.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=

View File

@@ -20,6 +20,9 @@ func (i *InmemCodec) ReadRequestHeader(req *rpc.Request) error {
}
func (i *InmemCodec) ReadRequestBody(args interface{}) error {
if args == nil {
return nil
}
sourceValue := reflect.Indirect(reflect.Indirect(reflect.ValueOf(i.Args)))
dst := reflect.Indirect(reflect.Indirect(reflect.ValueOf(args)))
dst.Set(sourceValue)

View File

@@ -3,8 +3,10 @@ package mock
import (
"fmt"
"math/rand"
"strings"
"time"
fake "github.com/brianvoe/gofakeit/v6"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/helper/envoy"
"github.com/hashicorp/nomad/helper/uuid"
@@ -2296,3 +2298,36 @@ func ServiceRegistrations() []*structs.ServiceRegistration {
},
}
}
func SecureVariable() *structs.SecureVariable {
envs := []string{"dev", "test", "prod"}
envIdx := rand.Intn(3)
env := envs[envIdx]
domain := fake.DomainName()
path := strings.ReplaceAll(env+"."+domain, ".", "/")
// owner := fake.Person()
createIdx := uint64(rand.Intn(100) + 100)
createDT := fake.DateRange(time.Now().AddDate(0, -1, 0), time.Now())
sv := &structs.SecureVariable{
Path: path,
Namespace: "default",
// CustomMeta: map[string]string{
// "owner_name": owner.FirstName + " " + owner.LastName,
// "owner_email": fmt.Sprintf("%v%s@%s", owner.FirstName[0], owner.LastName, domain),
// },
UnencryptedData: map[string]string{
"username": fake.Username(),
"password": fake.Password(true, true, true, true, false, 16),
},
CreateIndex: createIdx,
ModifyIndex: createIdx,
CreateTime: createDT,
ModifyTime: createDT,
}
// Flip a coin to see if we should return a "modified" object
if fake.Bool() {
sv.ModifyTime = fake.DateRange(sv.CreateTime, time.Now())
sv.ModifyIndex = sv.CreateIndex + uint64(rand.Intn(100))
}
return sv
}

View File

@@ -0,0 +1,111 @@
package nomad
import (
"strings"
"sync"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/nomad/structs"
)
var mvs MockVariableStore
type MockVariableStore struct {
m sync.RWMutex
backingMap map[string]*structs.SecureVariable
s *Server
l hclog.Logger
}
func (mvs *MockVariableStore) List(prefix string) []*structs.SecureVariableStub {
mvs.l.Info("***** List *****")
mvs.m.Lock()
mvs.m.Unlock()
if len(mvs.backingMap) == 0 {
return nil
}
vars := make([]*structs.SecureVariableStub, 0, len(mvs.backingMap))
for p, sVar := range mvs.backingMap {
if strings.HasPrefix(p, prefix) {
outVar := sVar.Stub()
vars = append(vars, &outVar)
}
}
return vars
}
func (mvs *MockVariableStore) Add(p string, bag structs.SecureVariable) {
mvs.l.Info("***** Add *****")
mvs.m.Lock()
mvs.m.Unlock()
nv := bag.Copy()
mvs.backingMap[p] = nv
}
func (mvs *MockVariableStore) Get(p string) *structs.SecureVariable {
mvs.l.Info("***** Get *****")
var out *structs.SecureVariable
mvs.m.Lock()
defer mvs.m.Unlock()
if v, ok := mvs.backingMap[p]; ok {
out = v.Copy()
} else {
return nil
}
return out
}
// Delete removes a key from the store. Removing a non-existent key is a no-op
func (mvs *MockVariableStore) Delete(p string) {
mvs.l.Info("***** Delete *****")
mvs.m.Lock()
defer mvs.m.Unlock()
delete(mvs.backingMap, p)
}
// Delete removes a key from the store. Removing a non-existent key is a no-op
func (mvs *MockVariableStore) Reset() {
mvs.l.Info("***** Reset *****")
mvs.m.Lock()
mvs.m.Unlock()
mvs.backingMap = make(map[string]*structs.SecureVariable)
}
func NewMockVariableStore(s *Server, l hclog.Logger) {
l.Info("***** Initializing mock variables backend *****")
mvs.m.Lock()
mvs.m.Unlock()
mvs.backingMap = make(map[string]*structs.SecureVariable)
mvs.s = s
mvs.l = l
}
func SV_List(args *structs.SecureVariablesListRequest, out *structs.SecureVariablesListResponse) {
out.Data = mvs.List(args.Prefix)
out.QueryMeta.KnownLeader = true
// TODO: Would be nice to at least have a forward moving number for index
// even in testing.
out.QueryMeta.Index = 999
out.QueryMeta.LastContact = 19
}
func SV_Upsert(args *structs.SecureVariablesUpsertRequest, out *structs.SecureVariablesUpsertResponse) {
nv := args.Data.Copy()
mvs.Add(nv.Path, *nv)
// TODO: Would be nice to at least have a forward moving number for index
// even in testing.
out.WriteMeta.Index = 9999
}
func SV_Read(args *structs.SecureVariablesReadRequest, out *structs.SecureVariablesReadResponse) {
out.Data = mvs.Get(args.Path)
// TODO: Would be nice to at least have a forward moving number for index
// even in testing.
out.Index = 9999
out.QueryMeta.KnownLeader = true
out.QueryMeta.Index = 999
out.QueryMeta.LastContact = 19
}
func SV_Delete(args *structs.SecureVariablesDeleteRequest, out *structs.SecureVariablesDeleteResponse) {
mvs.Delete(args.Path)
out.WriteMeta.Index = 9999
}

View File

@@ -0,0 +1,28 @@
package nomad
import (
"testing"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/stretchr/testify/require"
)
func TestMockVariables(t *testing.T) {
defer mvs.Reset()
sv1 := mock.SecureVariable()
mvs.Add(sv1.Path, *sv1)
out := mvs.List("")
require.NotNil(t, out)
require.Len(t, out, 1)
}
func TestDeleteMockVariables(t *testing.T) {
defer mvs.Reset()
sv1 := mock.SecureVariable()
mvs.Add(sv1.Path, *sv1)
out := mvs.List("")
require.NotNil(t, out)
require.Len(t, out, 1)
mvs.Delete(sv1.Path)
require.Empty(t, mvs.List(""))
}

View File

@@ -41,7 +41,13 @@ func (sv *SecureVariables) Create(args *structs.SecureVariablesUpsertRequest, re
if err != nil {
return err
}
args.Data.EncryptedData.Data = sv.encrypter.Encrypt(buf.Bytes(), "TODO")
args.Data.EncryptedData = &structs.SecureVariableData{}
args.Data.EncryptedData.KeyID = "TODO"
args.Data.EncryptedData.Data = sv.encrypter.Encrypt(buf.Bytes(), args.Data.EncryptedData.KeyID)
// TODO: implementation
SV_Upsert(args, reply)
return nil
}
@@ -61,6 +67,7 @@ func (sv *SecureVariables) List(args *structs.SecureVariablesListRequest, reply
}
// TODO: implementation
SV_List(args, reply)
return nil
}
@@ -80,6 +87,7 @@ func (sv *SecureVariables) Read(args *structs.SecureVariablesReadRequest, reply
}
// TODO: implementation
SV_Read(args, reply)
return nil
}
@@ -99,6 +107,27 @@ func (sv *SecureVariables) Update(args *structs.SecureVariablesUpsertRequest, re
}
// TODO: implementation
SV_Upsert(args, reply)
return nil
}
func (sv *SecureVariables) Delete(args *structs.SecureVariablesDeleteRequest, reply *structs.SecureVariablesDeleteResponse) error {
if done, err := sv.srv.forward("SecureVariables.Delete", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"nomad", "secure_variables", "delete"}, time.Now())
// TODO: implement real ACL checks
if aclObj, err := sv.srv.ResolveToken(args.AuthToken); err != nil {
return err
} else if aclObj != nil && !aclObj.IsManagement() {
return structs.ErrPermissionDenied
}
// TODO: implementation
SV_Delete(args, reply)
return nil
}

View File

@@ -287,6 +287,7 @@ type endpoints struct {
Enterprise *EnterpriseEndpoints
Event *Event
Namespace *Namespace
SecureVariables *SecureVariables
ServiceRegistration *ServiceRegistration
// Client endpoints
@@ -473,6 +474,10 @@ func NewServer(config *Config, consulCatalog consul.CatalogAPI, consulConfigEntr
// Start enterprise background workers
s.startEnterpriseBackground()
// FIXME: Remove once real implemenation exists
// Start Mock Secure Variables Server
go NewMockVariableStore(s, s.logger.Named("secure_variables"))
// Done
return s, nil
}
@@ -1171,6 +1176,7 @@ func (s *Server) setupRpcServer(server *rpc.Server, ctx *RPCContext) {
s.staticEndpoints.System = &System{srv: s, logger: s.logger.Named("system")}
s.staticEndpoints.Search = &Search{srv: s, logger: s.logger.Named("search")}
s.staticEndpoints.Namespace = &Namespace{srv: s}
s.staticEndpoints.SecureVariables = &SecureVariables{srv: s, logger: s.logger.Named("secure_variables"), encrypter: NewEncrypter()}
s.staticEndpoints.Enterprise = NewEnterpriseEndpoints(s)
// These endpoints are dynamic because they need access to the
@@ -1218,6 +1224,7 @@ func (s *Server) setupRpcServer(server *rpc.Server, ctx *RPCContext) {
server.Register(s.staticEndpoints.FileSystem)
server.Register(s.staticEndpoints.Agent)
server.Register(s.staticEndpoints.Namespace)
server.Register(s.staticEndpoints.SecureVariables)
// Create new dynamic endpoints and add them to the RPC server.
alloc := &Alloc{srv: s, ctx: ctx, logger: s.logger.Named("alloc")}

View File

@@ -18,8 +18,8 @@ type SecureVariable struct {
// Version uint64
// CustomMetaData map[string]string
EncryptedData *SecureVariableData // removed during serialization
UnencryptedData map[string]string // empty until serialized
EncryptedData *SecureVariableData `json:"-"` // removed during serialization
UnencryptedData map[string]string `json:"Items"` // empty until serialized
}
// SecureVariableData is the secret data for a Secure Variable
@@ -28,6 +28,61 @@ type SecureVariableData struct {
KeyID string // ID of root key used to encrypt this entry
}
func (sv SecureVariableData) Copy() *SecureVariableData {
out := make([]byte, 0, len(sv.Data))
copy(out, sv.Data)
return &SecureVariableData{
Data: out,
KeyID: sv.KeyID,
}
}
func (sv *SecureVariable) Copy() *SecureVariable {
if sv == nil {
return nil
}
out := *sv
if sv.UnencryptedData != nil {
out.UnencryptedData = make(map[string]string, len(sv.UnencryptedData))
for k, v := range sv.UnencryptedData {
out.UnencryptedData[k] = v
}
}
if sv.EncryptedData != nil {
out.EncryptedData = sv.EncryptedData.Copy()
}
return &out
}
func (sv SecureVariable) Stub() SecureVariableStub {
return SecureVariableStub{
Namespace: sv.Namespace,
Path: sv.Path,
CreateIndex: sv.CreateIndex,
CreateTime: sv.CreateTime,
ModifyIndex: sv.ModifyIndex,
ModifyTime: sv.ModifyTime,
}
}
// SecureVariableStub is the metadata envelope for a Secure Variable omitting
// the actual data. Intended to be used in list operations.
type SecureVariableStub struct {
Namespace string
Path string
CreateTime time.Time
CreateIndex uint64
ModifyIndex uint64
ModifyTime time.Time
// reserved for post-1.4.0 work
// LockIndex uint64
// Session string
// DeletedAt time.Time
// Version uint64
// CustomMetaData map[string]string
}
// SecureVariablesQuota is used to track the total size of secure
// variables entries per namespace. The total length of
// SecureVariable.EncryptedData will be added to the SecureVariablesQuota
@@ -54,7 +109,7 @@ type SecureVariablesListRequest struct {
}
type SecureVariablesListResponse struct {
Data []*SecureVariable
Data []*SecureVariableStub
QueryMeta
}