Skip to content

Commit

Permalink
Add support for Named Pipes in Windows (#190)
Browse files Browse the repository at this point in the history
* Introduce the WithNamedPipeName option to support named pipes (Windows only)

Signed-off-by: Agustín Martínez Fayó <[email protected]>
  • Loading branch information
amartinezfayo authored Apr 13, 2022
1 parent 7e161c4 commit 6fd5a8f
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 19 deletions.
1 change: 1 addition & 0 deletions v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/spiffe/go-spiffe/v2
go 1.13

require (
github.com/Microsoft/go-winio v0.5.2
github.com/stretchr/testify v1.7.1
github.com/zeebo/errs v1.2.2
google.golang.org/grpc v1.33.2
Expand Down
11 changes: 9 additions & 2 deletions v2/go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
Expand Down Expand Up @@ -33,7 +36,9 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
Expand All @@ -57,8 +62,10 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
44 changes: 44 additions & 0 deletions v2/internal/test/fakeworkloadapi/workload_api_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//go:build windows
// +build windows

package fakeworkloadapi

import (
"fmt"
"math/rand"
"testing"
"time"

"github.com/Microsoft/go-winio"
"github.com/spiffe/go-spiffe/v2/proto/spiffe/workload"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)

func NewWithNamedPipeListener(tb testing.TB) *WorkloadAPI {
w := &WorkloadAPI{
x509Chans: make(map[chan *workload.X509SVIDResponse]struct{}),
jwtBundlesChans: make(map[chan *workload.JWTBundlesResponse]struct{}),
}

listener, err := winio.ListenPipe(fmt.Sprintf(`\\.\pipe\go-spiffe-test-pipe-%x`, rand.Uint64()), nil)
require.NoError(tb, err)

server := grpc.NewServer()
workload.RegisterSpiffeWorkloadAPIServer(server, &workloadAPIWrapper{w: w})

w.wg.Add(1)
go func() {
defer w.wg.Done()
_ = server.Serve(listener)
}()

w.addr = listener.Addr().String()
tb.Logf("WorkloadAPI address: %s", w.addr)
w.server = server
return w
}

func init() {
rand.Seed(time.Now().UnixNano())
}
4 changes: 4 additions & 0 deletions v2/workloadapi/addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ func GetDefaultAddress() (string, bool) {
return os.LookupEnv(SocketEnv)
}

// ValidateAddress validates that the provided address
// can be parsed to a gRPC target string for dialing
// a Workload API endpoint exposed as either a Unix
// Domain Socket or TCP socket.
func ValidateAddress(addr string) error {
_, err := parseTargetFromAddr(addr)
return err
Expand Down
15 changes: 1 addition & 14 deletions v2/workloadapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,22 +218,9 @@ func (c *Client) ValidateJWTSVID(ctx context.Context, token, audience string) (*
return jwtsvid.ParseInsecure(token, []string{audience})
}

func (c *Client) setAddress() error {
if c.config.address == "" {
var ok bool
c.config.address, ok = GetDefaultAddress()
if !ok {
return errors.New("workload endpoint socket address is not configured")
}
}

var err error
c.config.address, err = parseTargetFromAddr(c.config.address)
return err
}

func (c *Client) newConn(ctx context.Context) (*grpc.ClientConn, error) {
c.config.dialOptions = append(c.config.dialOptions, grpc.WithInsecure())
c.appendDialOptionsOS()
return grpc.DialContext(ctx, c.config.address, c.config.dialOptions...)
}

Expand Down
29 changes: 29 additions & 0 deletions v2/workloadapi/client_posix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//go:build !windows
// +build !windows

package workloadapi

import "errors"

// appendDialOptionsOS appends OS specific dial options
func (c *Client) appendDialOptionsOS() {
// No options to add in this platform
}
func (c *Client) setAddress() error {
if c.config.namedPipeName != "" {
// Purely defensive. This should never happen.
return errors.New("named pipes not supported in this platform")
}

if c.config.address == "" {
var ok bool
c.config.address, ok = GetDefaultAddress()
if !ok {
return errors.New("workload endpoint socket address is not configured")
}
}

var err error
c.config.address, err = parseTargetFromAddr(c.config.address)
return err
}
50 changes: 50 additions & 0 deletions v2/workloadapi/client_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//go:build windows
// +build windows

package workloadapi

import (
"errors"
"path/filepath"

"github.com/Microsoft/go-winio"
"google.golang.org/grpc"
)

// appendDialOptionsOS appends OS specific dial options
func (c *Client) appendDialOptionsOS() {
if c.config.namedPipeName != "" {
// Use the dialer to connect to named pipes only if a named pipe
// is defined (i.e. WithNamedPipeName is used).
c.config.dialOptions = append(c.config.dialOptions, grpc.WithContextDialer(winio.DialPipeContext))
}
}

func (c *Client) setAddress() error {
var err error
if c.config.namedPipeName != "" {
if c.config.address != "" {
return errors.New("only one of WithAddr or WithNamedPipeName options can be used, not both")
}
c.config.address = namedPipeTarget(c.config.namedPipeName)
return nil
}

if c.config.address == "" {
var ok bool
c.config.address, ok = GetDefaultAddress()
if !ok {
return errors.New("workload endpoint socket address is not configured")
}
}

c.config.address, err = parseTargetFromAddr(c.config.address)
return err
}

// namedPipeTarget returns a target string suitable for
// dialing the endpoint address based on the provided
// pipe name.
func namedPipeTarget(pipeName string) string {
return `\\.\` + filepath.Join("pipe", pipeName)
}
53 changes: 53 additions & 0 deletions v2/workloadapi/client_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//go:build windows
// +build windows

package workloadapi

import (
"context"
"strings"
"testing"

"github.com/spiffe/go-spiffe/v2/internal/test"
"github.com/spiffe/go-spiffe/v2/internal/test/fakeworkloadapi"
"github.com/stretchr/testify/require"
)

func TestWithNamedPipeName(t *testing.T) {
ca := test.NewCA(t, td)
wl := fakeworkloadapi.NewWithNamedPipeListener(t)
defer wl.Stop()

pipeName := getPipeName(wl.Addr())
c, err := New(context.Background(), WithNamedPipeName(pipeName))
require.NoError(t, err)
defer c.Close()
require.Equal(t, pipeName, c.config.namedPipeName)

resp := &fakeworkloadapi.X509SVIDResponse{
Bundle: ca.X509Bundle(),
SVIDs: makeX509SVIDs(ca, fooID, barID),
}
wl.SetX509SVIDResponse(resp)
svid, err := c.FetchX509SVID(context.Background())
require.NoError(t, err)
assertX509SVID(t, svid, fooID, resp.SVIDs[0].Certificates)
}

func TestWithNamedPipeNameError(t *testing.T) {
wl := fakeworkloadapi.NewWithNamedPipeListener(t)
defer wl.Stop()

c, err := New(context.Background(), WithNamedPipeName("ohno"))
require.NoError(t, err)
defer c.Close()

wl.SetX509SVIDResponse(&fakeworkloadapi.X509SVIDResponse{})
_, err = c.FetchX509SVID(context.Background())
require.Error(t, err)
require.Contains(t, err.Error(), `ohno: The system cannot find the file specified`)
}

func getPipeName(s string) string {
return strings.TrimPrefix(s, `\\.\pipe`)
}
7 changes: 4 additions & 3 deletions v2/workloadapi/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ type BundleSourceOption interface {
}

type clientConfig struct {
address string
dialOptions []grpc.DialOption
log logger.Logger
address string
namedPipeName string
dialOptions []grpc.DialOption
log logger.Logger
}

type clientOption func(*clientConfig)
Expand Down
12 changes: 12 additions & 0 deletions v2/workloadapi/option_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build windows
// +build windows

package workloadapi

// WithNamedPipeName provides a Pipe Name for the Workload API
// endpoint in the form \\.\pipe\<pipeName>.
func WithNamedPipeName(pipeName string) ClientOption {
return clientOption(func(c *clientConfig) {
c.namedPipeName = pipeName
})
}

0 comments on commit 6fd5a8f

Please sign in to comment.