Initial BYOW auth PoC (#29)

This commit is contained in:
Jeff Mitchell committed 2022-05-09 13:28:17 -04:00
1 parent 8ed560fc3e
commit f748af7b02
21 files changed
+707 -404

No files matched your search

+22
View File
@@ -36,6 +36,9 @@ import (
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-secure-stdlib/mlock"
"github.com/hashicorp/go-secure-stdlib/pluginutil/v2"
"github.com/hashicorp/nodeenrollment/noderegistration"
nodeefile "github.com/hashicorp/nodeenrollment/nodestorage/file"
"github.com/hashicorp/nodeenrollment/nodetypes"
ua "go.uber.org/atomic"
"google.golang.org/grpc"
)
@@ -86,6 +89,9 @@ type Controller struct {
// Used to signal the Health Service to start
// replying to queries with "503 Service Unavailable".
HealthService *health.Service
// PoC: Testing bits for BYOW
NodeeFileStorage *nodeefile.FileStorage
}
func New(ctx context.Context, conf *Config) (*Controller, error) {
@@ -299,6 +305,7 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
c.ConnectionRepoFn = func() (*session.ConnectionRepository, error) {
return session.NewConnectionRepository(ctx, dbase, dbase, c.kms)
}
return c, nil
}
@@ -309,6 +316,17 @@ func (c *Controller) Start() error {
return nil
}
c.baseContext, c.baseCancel = context.WithCancel(context.Background())
var err error
c.NodeeFileStorage, err = nodeefile.NewFileStorage(c.baseContext)
if err != nil {
return err
}
_, err = nodetypes.RotateRootCertificates(c.baseContext, c.NodeeFileStorage)
if err != nil {
return err
}
if err := c.registerJobs(); err != nil {
return fmt.Errorf("error registering jobs: %w", err)
}
@@ -391,3 +409,7 @@ func (c *Controller) Shutdown() error {
func (c *Controller) WorkerStatusUpdateTimes() *sync.Map {
return c.workerStatusUpdateTimes
}
func (c *Controller) AuthorizeNodeeWorker(keyId string) error {
return noderegistration.AuthorizeNode(c.baseContext, c.NodeeFileStorage, keyId)
}
+65
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/textproto"
@@ -42,6 +43,7 @@ import (
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-secure-stdlib/listenerutil"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/hashicorp/nodeenrollment/nodetypes"
"github.com/mr-tron/base58"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@@ -65,6 +67,7 @@ func (c *Controller) apiHandler(props HandlerProperties) (http.Handler, error) {
if err != nil {
return nil, err
}
mux.Handle("/v1/nodes", handleNodes(c))
mux.Handle("/v1/", grpcGwMux)
mux.Handle("/", handleUi(c))
@@ -561,6 +564,68 @@ func wrapHandlerWithCallbackInterceptor(h http.Handler, c *Controller) http.Hand
})
}
func handleNodes(c *Controller) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
type vals struct {
WaitingNodes []string `json:"waiting_nodes"`
}
var err error
var currVals vals
currVals.WaitingNodes, err = c.NodeeFileStorage.List(c.baseContext, (*nodetypes.NodeInformation)(nil))
if err != nil {
_, _ = w.Write([]byte(err.Error()))
w.WriteHeader(500)
return
}
ret, err := json.Marshal(currVals)
if err != nil {
_, _ = w.Write([]byte(err.Error()))
w.WriteHeader(500)
return
}
_, err = w.Write(ret)
if err != nil {
_, _ = w.Write([]byte(err.Error()))
w.WriteHeader(500)
return
}
w.WriteHeader(200)
return
case http.MethodPost:
body, err := io.ReadAll(req.Body)
req.Body.Close()
if err != nil {
_, _ = w.Write([]byte(err.Error()))
w.WriteHeader(500)
return
}
type val struct {
KeyId string `json:"key_id"`
}
var currVal val
if err := json.Unmarshal(body, &currVal); err != nil {
_, _ = w.Write([]byte(err.Error()))
w.WriteHeader(500)
return
}
if err := c.AuthorizeNodeeWorker(currVal.KeyId); err != nil {
_, _ = w.Write([]byte(err.Error()))
w.WriteHeader(500)
return
}
w.WriteHeader(204)
return
default:
w.WriteHeader(http.StatusBadRequest)
return
}
})
}
/*
func WrapForwardedForHandler(h http.Handler, authorizedAddrs []*sockaddr.SockAddrMarshaler, rejectNotPresent, rejectNonAuthz bool, hopSkips int) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -0,0 +1,45 @@
package workers
import (
"context"
"fmt"
pbs "github.com/hashicorp/nodeenrollment/multihop"
"github.com/hashicorp/nodeenrollment/nodeauth"
"github.com/hashicorp/nodeenrollment/noderegistration"
"github.com/hashicorp/nodeenrollment/nodetypes"
)
type multihopServiceServer struct {
pbs.UnimplementedMultihopServiceServer
currentParams nodeauth.CurrentParameterFactory
}
func NewMultihopServiceServer(
currentParams nodeauth.CurrentParameterFactory,
) *multihopServiceServer {
return &multihopServiceServer{
currentParams: currentParams,
}
}
var _ pbs.MultihopServiceServer = (*multihopServiceServer)(nil)
func (m *multihopServiceServer) FetchNodeCredentials(ctx context.Context, req *nodetypes.FetchNodeCredentialsRequest) (*nodetypes.FetchNodeCredentialsResponse, error) {
const op = "workers.(multihopServiceServer).FetchNodeCredentials"
_, storage, opt, err := m.currentParams()
if err != nil {
return nil, fmt.Errorf("%s: error getting current parameters: %w", op, err)
}
return noderegistration.FetchNodeCredentials(ctx, storage, req, opt...)
}
func (m *multihopServiceServer) GenerateServerCertificates(ctx context.Context, req *nodetypes.GenerateServerCertificatesRequest) (*nodetypes.GenerateServerCertificatesResponse, error) {
const op = "workers.(multihopServiceServer).GenerateServerCertificates"
_, storage, opt, err := m.currentParams()
if err != nil {
return nil, fmt.Errorf("%s: error getting current parameters: %w", op, err)
}
return noderegistration.GenerateServerCertificates(ctx, storage, req, opt...)
}
@@ -1,12 +1,15 @@
package controller
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"strings"
"github.com/hashicorp/boundary/internal/observability/event"
nodee "github.com/hashicorp/nodeenrollment"
"github.com/hashicorp/nodeenrollment/nodeauth"
)
// interceptingListener allows us to validate the nonce from a connection before
@@ -45,42 +48,54 @@ func newInterceptingListener(c *Controller, baseLn net.Listener) *interceptingLi
func (m *interceptingListener) Accept() (net.Conn, error) {
const op = "controller.(interceptingListener).Accept"
ctx := context.TODO()
ctx := m.c.baseContext
conn, err := m.baseLn.Accept()
if err != nil {
// We may already be closed but can't be sure, so just try anyways
if conn != nil {
if err := conn.Close(); err != nil {
event.WriteError(context.TODO(), op, err, event.WithInfoMsg("error closing worker connection"))
event.WriteError(ctx, op, err, event.WithInfoMsg("error closing worker connection"))
}
}
return nil, err
}
nonce := make([]byte, 20)
read, err := conn.Read(nonce)
if err != nil {
if err := conn.Close(); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error closing worker connection"))
tlsConn := conn.(*tls.Conn)
switch {
case nodeauth.ContainsNodeAuthAlpnProto(tlsConn.ConnectionState().NegotiatedProtocol):
event.WriteSysEvent(ctx, op, "worker successfully authed", "key_id", nodee.KeyIdFromPkix(tlsConn.ConnectionState().PeerCertificates[0].SubjectKeyId))
return conn, nil
case strings.HasPrefix(tlsConn.ConnectionState().NegotiatedProtocol, "v1workerauth"):
nonce := make([]byte, 20)
read, err := conn.Read(nonce)
if err != nil {
if err := conn.Close(); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error closing worker connection"))
}
return nil, fmt.Errorf("error reading nonce from connection: %w", err)
}
return nil, fmt.Errorf("error reading nonce from connection: %w", err)
}
if read != len(nonce) {
if err := conn.Close(); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error closing worker connection"))
if read != len(nonce) {
if err := conn.Close(); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error closing worker connection"))
}
return nil, fmt.Errorf("error reading nonce from worker, expected %d bytes, got %d", 20, read)
}
return nil, fmt.Errorf("error reading nonce from worker, expected %d bytes, got %d", 20, read)
}
workerInfoRaw, found := m.c.workerAuthCache.Load(string(nonce))
if !found {
if err := conn.Close(); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error closing worker connection"))
workerInfoRaw, found := m.c.workerAuthCache.Load(string(nonce))
if !found {
if err := conn.Close(); err != nil {
event.WriteError(ctx, op, err, event.WithInfoMsg("error closing worker connection"))
}
return nil, errors.New("did not find valid nonce for incoming worker")
}
return nil, errors.New("did not find valid nonce for incoming worker")
workerInfo := workerInfoRaw.(*workerAuthEntry)
workerInfo.conn = tlsConn
event.WriteSysEvent(ctx, op, "worker successfully authed", "name", workerInfo.Name)
return tlsConn, nil
default:
return nil, nodeauth.NewTempError(errors.New("unable to authenticate incoming connection"))
}
workerInfo := workerInfoRaw.(*workerAuthEntry)
workerInfo.conn = conn
event.WriteSysEvent(ctx, op, "worker successfully authed", "name", workerInfo.Name)
return conn, nil
}
func (m *interceptingListener) Close() error {
+24 -3
View File
@@ -17,6 +17,9 @@ import (
"github.com/hashicorp/boundary/internal/servers/controller/handlers/workers"
"github.com/hashicorp/boundary/internal/servers/controller/internal/metric"
"github.com/hashicorp/go-multierror"
nodee "github.com/hashicorp/nodeenrollment"
"github.com/hashicorp/nodeenrollment/multihop"
"github.com/hashicorp/nodeenrollment/nodeauth"
"google.golang.org/grpc"
)
@@ -103,9 +106,21 @@ func (c *Controller) configureForApi(ln *base.ServerListener) ([]func(), error)
}
func (c *Controller) configureForCluster(ln *base.ServerListener) (func(), error) {
l := tls.NewListener(ln.ClusterListener, &tls.Config{
GetConfigForClient: c.validateWorkerTls,
})
l, err := nodeauth.NewInterceptingListener(
ln.ClusterListener,
&tls.Config{
GetConfigForClient: c.validateWorkerTls,
},
nodeauth.MakeCurrentParametersFactory(
c.baseContext,
nodee.NopTransactionStorage(c.NodeeFileStorage),
),
nil,
nil,
)
if err != nil {
return nil, fmt.Errorf("error instantiating node auth listener: %w", err)
}
workerReqInterceptor, err := workerRequestInfoInterceptor(c.baseContext, c.conf.Eventer)
if err != nil {
@@ -129,6 +144,12 @@ func (c *Controller) configureForCluster(ln *base.ServerListener) (func(), error
c.workerStatusUpdateTimes, c.kms)
pbs.RegisterServerCoordinationServiceServer(workerServer, workerService)
pbs.RegisterSessionServiceServer(workerServer, workerService)
multihopService := workers.NewMultihopServiceServer(
nodeauth.MakeCurrentParametersFactory(c.baseContext, nodee.NopTransactionStorage(c.NodeeFileStorage)),
)
multihop.RegisterMultihopServiceServer(workerServer, multihopService)
metric.InitializeClusterCollectors(c.conf.PrometheusRegisterer, workerServer)
ln.GrpcServer = workerServer
@@ -27,6 +27,7 @@ type workerAuthEntry struct {
// been replayed, then stores connection information into the auth cache, which
// is used by the intercepting listener to log information.
func (c Controller) validateWorkerTls(hello *tls.ClientHelloInfo) (*tls.Config, error) {
const op = "controller.(Controller).validateWorkerTls"
for _, p := range hello.SupportedProtos {
switch {
case strings.HasPrefix(p, "v1workerauth-"):
@@ -50,7 +51,7 @@ func (c Controller) validateWorkerTls(hello *tls.ClientHelloInfo) (*tls.Config,
return tlsConf, err
}
}
return nil, nil
return nil, errors.New("no supported protos found in hello")
}
// v1WorkerAuthConfig: