From c4504ce5b5569c53d5c7cdb5658a274680b15c93 Mon Sep 17 00:00:00 2001 From: Maartje Eyskens Date: Mon, 14 Oct 2024 14:13:13 +0200 Subject: [PATCH] Move mini-spire into cofidectl Signed-off-by: Maartje Eyskens --- cmd/cofidectl/cmd/dev/dev.go | 20 ++ cmd/cofidectl/cmd/dev/mini-spire.go | 86 +++++++ cmd/cofidectl/cmd/root.go | 2 + go.mod | 38 +-- go.sum | 82 +++--- internal/pkg/dev/minispire/grpc-authinfo.go | 116 +++++++++ internal/pkg/dev/minispire/jwt.go | 48 ++++ internal/pkg/dev/minispire/memory-ca.go | 272 ++++++++++++++++++++ internal/pkg/dev/minispire/workload.go | 261 +++++++++++++++++++ 9 files changed, 873 insertions(+), 52 deletions(-) create mode 100644 cmd/cofidectl/cmd/dev/dev.go create mode 100644 cmd/cofidectl/cmd/dev/mini-spire.go create mode 100644 internal/pkg/dev/minispire/grpc-authinfo.go create mode 100644 internal/pkg/dev/minispire/jwt.go create mode 100644 internal/pkg/dev/minispire/memory-ca.go create mode 100644 internal/pkg/dev/minispire/workload.go diff --git a/cmd/cofidectl/cmd/dev/dev.go b/cmd/cofidectl/cmd/dev/dev.go new file mode 100644 index 0000000..b9c4e9e --- /dev/null +++ b/cmd/cofidectl/cmd/dev/dev.go @@ -0,0 +1,20 @@ +package dev + +import ( + "github.com/spf13/cobra" +) + +var federationDesc = ` +This command consists of multiple subcommands to administer the Cofide local development environment +` + +func NewDevCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "dev mini-spire [ARGS]", + Short: "setup a local development spire", + Long: federationDesc, + Args: cobra.NoArgs, + } + cmd.AddCommand(devMiniSpireCmd()) + return cmd +} diff --git a/cmd/cofidectl/cmd/dev/mini-spire.go b/cmd/cofidectl/cmd/dev/mini-spire.go new file mode 100644 index 0000000..6854a73 --- /dev/null +++ b/cmd/cofidectl/cmd/dev/mini-spire.go @@ -0,0 +1,86 @@ +package dev + +import ( + "fmt" + "net" + "os" + "os/signal" + "syscall" + + "github.com/cofide/cofidectl/internal/pkg/dev/minispire" + "github.com/spf13/cobra" + pb "github.com/spiffe/go-spiffe/v2/proto/spiffe/workload" + "google.golang.org/grpc" +) + +var devMiniSpireDesc = ` +This command will bring up a local SPIRE workload API socket that sets up a local development CA and issues SVIDs to every client connecting to it. +THIS COMMAND SHOULD NEVER BE USED IN ANY PRODUCTION OR SERVER ENVIRONMENT. +` + +type devMiniSpireOpts struct { + socket string + domain string + keyType string +} + +func devMiniSpireCmd() *cobra.Command { + opts := devMiniSpireOpts{} + + cmd := &cobra.Command{ + Use: "mini-spire [ARGS]", + Short: "Sets up a SPIRE agent for local development", + Long: devMiniSpireDesc, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("Building in-memory CA") + var kt minispire.KeyType + if opts.keyType == "rsa" { + kt = minispire.KeyTypeRSA + } else if opts.keyType == "ecdsa" { + kt = minispire.KeyTypeECDSAP256 + } else { + return fmt.Errorf("key type %q is unknown", opts.keyType) + } + ca, err := minispire.NewInMemoryCA(kt) + if err != nil { + return fmt.Errorf("failed to create in-memory CA: %v", err) + } + + fmt.Println("Starting SPIRE server") + lis, err := net.Listen("unix", opts.socket) + if err != nil { + return fmt.Errorf("failed to listen in %q: %v", opts.socket, err) + } + + grpcServer := grpc.NewServer(grpc.Creds(minispire.NewCredentials())) + wl := minispire.NewWorkloadHandler(minispire.Config{ + Domain: opts.domain, + CA: ca, + }) + pb.RegisterSpiffeWorkloadAPIServer(grpcServer, wl) + + go func() { + fmt.Println("SPIRE server listening on", opts.socket) + grpcServer.Serve(lis) + }() + + // listen for signals to stop the server + osSignals := make(chan os.Signal, 1) + signal.Notify(osSignals, syscall.SIGINT, syscall.SIGTERM) + <-osSignals + + fmt.Println("Shutting down server") + lis.Close() + + return nil + }, + } + + f := cmd.Flags() + f.StringVarP(&opts.domain, "domain", "d", "example.com", "Trust domain to use for this trust zone") + f.StringVarP(&opts.socket, "socket", "s", "/tmp/spire.sock", "Path to the UNIX socket to listen on") + f.StringVarP(&opts.keyType, "key-type", "k", "rsa", "Key type to use for the CA (rsa or ecdsa)") + + return cmd +} diff --git a/cmd/cofidectl/cmd/root.go b/cmd/cofidectl/cmd/root.go index 42709aa..75bf2ee 100644 --- a/cmd/cofidectl/cmd/root.go +++ b/cmd/cofidectl/cmd/root.go @@ -10,6 +10,7 @@ import ( "github.com/cofide/cofidectl/cmd/cofidectl/cmd/apbinding" "github.com/cofide/cofidectl/cmd/cofidectl/cmd/attestationpolicy" cmdcontext "github.com/cofide/cofidectl/cmd/cofidectl/cmd/context" + "github.com/cofide/cofidectl/cmd/cofidectl/cmd/dev" "github.com/cofide/cofidectl/cmd/cofidectl/cmd/federation" "github.com/cofide/cofidectl/cmd/cofidectl/cmd/trustzone" "github.com/cofide/cofidectl/cmd/cofidectl/cmd/workload" @@ -64,6 +65,7 @@ func (r *RootCommand) GetRootCommand() (*cobra.Command, error) { wlCmd.GetRootCommand(), upCmd.UpCmd(), downCmd.DownCmd(), + dev.NewDevCmd(), ) return cmd, nil diff --git a/go.mod b/go.mod index 6f4cb1b..318bd0b 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,21 @@ module github.com/cofide/cofidectl -go 1.22.7 +go 1.23.2 require ( buf.build/go/protoyaml v0.2.0 cuelang.org/go v0.10.0 github.com/cofide/cofide-api-sdk v0.2.0 - github.com/fatih/color v1.13.0 + github.com/cofide/cofide-sdk-go v0.0.0-unpublished + github.com/fatih/color v1.16.0 + github.com/go-jose/go-jose/v4 v4.0.4 github.com/gofrs/flock v0.12.1 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-plugin v1.6.2 github.com/manifoldco/promptui v0.9.0 github.com/spf13/cobra v1.8.1 github.com/spiffe/go-spiffe/v2 v2.4.0 + github.com/spiffe/spire v1.11.0 github.com/spiffe/spire-api-sdk v1.10.4 github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.67.1 @@ -23,6 +26,8 @@ require ( // Uncomment the following for development with local Cofide API SDK changes: //replace github.com/cofide/cofide-api-sdk => ../cofide-api-sdk +replace github.com/cofide/cofide-sdk-go v0.0.0-unpublished => ../cofide-sdk-go + require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2 // indirect dario.cat/mergo v1.0.1 // indirect @@ -51,9 +56,9 @@ require ( github.com/cyphar/filepath-securejoin v0.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v27.1.0+incompatible // indirect + github.com/docker/cli v27.1.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/docker v27.3.1+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect @@ -72,9 +77,9 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.0.1 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.21.0 // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect @@ -120,7 +125,7 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.20.4 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -138,16 +143,15 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect + github.com/zeebo/errs v1.3.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.49.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/crypto v0.28.0 // indirect - golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect @@ -155,9 +159,9 @@ require ( golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.25.0 // indirect golang.org/x/text v0.19.0 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + golang.org/x/time v0.7.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 86b3638..f7c9adf 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZ github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= @@ -115,12 +117,12 @@ github.com/distribution/distribution/v3 v3.0.0-beta.1 h1:X+ELTxPuZ1Xe5MsD3kp2wfG github.com/distribution/distribution/v3 v3.0.0-beta.1/go.mod h1:O9O8uamhHzWWQVTjuQpyYUVm/ShPHPUDgvQMpHGVBDs= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.1.0+incompatible h1:P0KSYmPtNbmx59wHZvG6+rjivhKDRA1BvvWM0f5DgHc= -github.com/docker/cli v27.1.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= +github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -147,8 +149,9 @@ github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lSh github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= @@ -162,6 +165,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -210,12 +215,12 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -260,7 +265,7 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= @@ -297,6 +302,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -321,8 +328,9 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= @@ -336,8 +344,8 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= +github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -384,8 +392,8 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -433,6 +441,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spiffe/go-spiffe/v2 v2.4.0 h1:j/FynG7hi2azrBG5cvjRcnQ4sux/VNj8FAVc99Fl66c= github.com/spiffe/go-spiffe/v2 v2.4.0/go.mod h1:m5qJ1hGzjxjtrkGHZupoXHo/FDWwCB1MdSyBzfHugx0= +github.com/spiffe/spire v1.11.0 h1:aUtgZxp03IdjAYk9xiKB4mutmWg5KiaY+q/r0mCq7lw= +github.com/spiffe/spire v1.11.0/go.mod h1:RqMc7c1Iev739s8ak00C6M8Xh1y4U4z0Lar8XEg4idY= github.com/spiffe/spire-api-sdk v1.10.4 h1:XdRFd2T7tJzq045SF3sxQNIViiXt0rStIa6kzxhSgaM= github.com/spiffe/spire-api-sdk v1.10.4/go.mod h1:4uuhFlN6KBWjACRP3xXwrOTNnvaLp1zJs8Lribtr4fI= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= @@ -467,14 +477,16 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= +github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 h1:ysCfPZB9AjUlMa1UHYup3c9dAOCMQX/6sxSfPBUoxHw= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1/go.mod h1:ha0aiYm+DOPsLHjh0zoQ8W8sLT+LJ58J3j47lGpSLrU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= @@ -491,14 +503,14 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgY go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= -go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= @@ -513,8 +525,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -578,8 +590,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -600,10 +612,10 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/internal/pkg/dev/minispire/grpc-authinfo.go b/internal/pkg/dev/minispire/grpc-authinfo.go new file mode 100644 index 0000000..4c0f5cc --- /dev/null +++ b/internal/pkg/dev/minispire/grpc-authinfo.go @@ -0,0 +1,116 @@ +package minispire + +import ( + "context" + "errors" + "fmt" + "log" + "net" + "os" + "path/filepath" + "syscall" + + "google.golang.org/grpc/credentials" +) + +var ( + ErrInvalidConnection = errors.New("invalid connection") +) + +type Conn struct { + net.Conn + Info AuthInfo +} + +func (c *Conn) Close() error { + return c.Conn.Close() +} + +type grpcCredentials struct{} + +func NewCredentials() credentials.TransportCredentials { + return &grpcCredentials{} +} + +func (c *grpcCredentials) ClientHandshake(_ context.Context, _ string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) { + conn.Close() + return conn, AuthInfo{}, ErrInvalidConnection +} + +func (c *grpcCredentials) ServerHandshake(conn net.Conn) (net.Conn, credentials.AuthInfo, error) { + wrappedCon, ok := conn.(*net.UnixConn) + if !ok { + conn.Close() + log.Printf("invalid connection type: %T", conn) + return conn, AuthInfo{}, ErrInvalidConnection + } + // get the caller's PID, UID, and GID + sys, err := wrappedCon.SyscallConn() + if err != nil { + log.Printf("unable to get peer credentials: %v", err) + conn.Close() + return conn, AuthInfo{}, ErrInvalidConnection + } + + auth := AuthInfo{} + sys.Control(func(fd uintptr) { + cred, err := syscall.GetsockoptUcred(int(fd), syscall.SOL_SOCKET, syscall.SO_PEERCRED) + if err != nil { + log.Printf("unable to get peer credentials: %v", err) + return + } + auth.Caller.PID = int32(cred.Pid) + auth.Caller.UID = uint32(cred.Uid) + auth.Caller.GID = uint32(cred.Gid) + }) + + // get binary path of the caller + path, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", auth.Caller.PID)) + if err == nil { + _, auth.Caller.BinaryName = filepath.Split(path) + } + + return wrappedCon, AuthInfo{ + Caller: CallerInfo{ + PID: auth.Caller.PID, + UID: auth.Caller.UID, + GID: auth.Caller.GID, + BinaryName: auth.Caller.BinaryName, + }, + }, nil +} + +func (c *grpcCredentials) Info() credentials.ProtocolInfo { + return credentials.ProtocolInfo{ + SecurityProtocol: "spire-attestation", + SecurityVersion: "0.2", + ServerName: "spire-agent", + } +} + +func (c *grpcCredentials) Clone() credentials.TransportCredentials { + credentialsCopy := *c + return &credentialsCopy +} + +func (c *grpcCredentials) OverrideServerName(_ string) error { + return nil +} + +type CallerInfo struct { + Addr net.Addr + PID int32 + UID uint32 + GID uint32 + BinaryName string +} + +type AuthInfo struct { + Caller CallerInfo +} + +// AuthType returns the authentication type and allows us to +// conform to the gRPC AuthInfo interface +func (AuthInfo) AuthType() string { + return "spire-attestation" +} diff --git a/internal/pkg/dev/minispire/jwt.go b/internal/pkg/dev/minispire/jwt.go new file mode 100644 index 0000000..acab763 --- /dev/null +++ b/internal/pkg/dev/minispire/jwt.go @@ -0,0 +1,48 @@ +package minispire + +import ( + "crypto" + "time" + + "github.com/go-jose/go-jose/v4" + "github.com/spiffe/go-spiffe/v2/spiffeid" +) + +type JWTKey struct { + // The signer used to sign keys + Signer crypto.Signer + + // Kid is the JWT key ID (i.e. "kid" claim) + Kid string + + // NotAfter is the expiration time of the JWT key. + NotAfter time.Time +} + +// WorkloadJWTSVIDParams are parameters relevant to workload JWT-SVID creation +type WorkloadJWTSVIDParams struct { + // SPIFFE ID of the SVID + SPIFFEID spiffeid.ID + + // TTL is the desired time-to-live of the SVID. Regardless of the TTL, the + // lifetime of the token will be capped to that of the signing key. + TTL time.Duration + + // Audience is used for audience claims + Audience []string +} + +// WorkloadJWTSVIDParams are parameters relevant to workload JWT PoP creation +type WorkloadJWTPOParams struct { + // SPIFFE ID of the SVID + SPIFFEID spiffeid.ID + + // TTL is the desired time-to-live of the SVID. Regardless of the TTL, the + // lifetime of the token will be capped to that of the signing key. + TTL time.Duration + + // Audience is used for audience claims + Audience string + + Key jose.JSONWebKey +} diff --git a/internal/pkg/dev/minispire/memory-ca.go b/internal/pkg/dev/minispire/memory-ca.go new file mode 100644 index 0000000..5a3ff61 --- /dev/null +++ b/internal/pkg/dev/minispire/memory-ca.go @@ -0,0 +1,272 @@ +package minispire + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "errors" + "fmt" + "math/big" + "time" + + "github.com/go-jose/go-jose/v4" + "github.com/go-jose/go-jose/v4/cryptosigner" + "github.com/go-jose/go-jose/v4/jwt" + "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/spire/pkg/common/cryptoutil" + "github.com/spiffe/spire/pkg/common/jwtsvid" +) + +// This package implements a simple in-memory CA for SPIRE. +// It generates a CA and signs SVIDs on the fly for local development purposes. + +type InMemoryCA struct { + caKey crypto.Signer + caCert *x509.Certificate + caCertBytes []byte + jwtKey JWTKey + + KeyType KeyType +} + +type KeyType int + +const ( + KeyTypeRSA KeyType = iota + KeyTypeECDSAP256 +) + +func NewInMemoryCA(kt KeyType) (*InMemoryCA, error) { + var caKey crypto.Signer + if kt == KeyTypeRSA { + var err error + caKey, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, fmt.Errorf("failed to generate CA key: %v", err) + } + } else if kt == KeyTypeECDSAP256 { + var err error + caKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate CA key: %v", err) + } + } + caSerial, err := rand.Int(rand.Reader, big.NewInt(100000)) + if err != nil { + return nil, fmt.Errorf("failed to generate CA serial: %v", err) + } + caCert := &x509.Certificate{ + Subject: pkix.Name{ + Organization: []string{"Cofide Development"}, + Country: []string{"Earth"}, + Province: []string{""}, + Locality: []string{""}, + StreetAddress: []string{""}, + PostalCode: []string{""}, + }, + NotAfter: time.Now().Add(time.Hour * 24 * 30), // setting 30 days to avoid people using this outside of development, no laptop lasts that long + SerialNumber: caSerial, + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + caCertBytes, err := x509.CreateCertificate(rand.Reader, caCert, caCert, caKey.Public(), caKey) + if err != nil { + return nil, fmt.Errorf("failed to create CA cert: %v", err) + } + + return &InMemoryCA{ + KeyType: kt, + caKey: caKey, + caCert: caCert, + caCertBytes: caCertBytes, + + jwtKey: JWTKey{ + Signer: caKey, + Kid: "kid", + NotAfter: caCert.NotAfter, + }, + }, nil +} + +func (i *InMemoryCA) Sign(csrBytes []byte) ([]byte, time.Time, error) { + svidSerial, err := rand.Int(rand.Reader, big.NewInt(100000)) + if err != nil { + return nil, time.Now(), fmt.Errorf("failed to generate SVID serial: %v", err) + } + + // parse CSR + csr, err := x509.ParseCertificateRequest(csrBytes) + if err != nil { + return nil, time.Now(), fmt.Errorf("failed to parse CSR: %v", err) + } + + // create SVID + svid := &x509.Certificate{ + URIs: csr.URIs, + NotAfter: time.Now().Add(4 * time.Minute), // set to 4 minutes for testing of rotation + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + SerialNumber: svidSerial, + } + + // sign SVID + svidBytes, err := x509.CreateCertificate(rand.Reader, svid, i.caCert, csr.PublicKey, i.caKey) + if err != nil { + return nil, time.Now(), fmt.Errorf("failed to sign SVID: %v", err) + } + + return svidBytes, svid.NotAfter, nil +} + +func (i *InMemoryCA) GetCACert() []byte { + return i.caCertBytes +} + +func (i *InMemoryCA) SignWorkloadJWTSVID(ctx context.Context, params WorkloadJWTSVIDParams) (string, error) { + if params.TTL == 0 { + params.TTL = time.Minute * 5 + } + + claims := map[string]any{ + "sub": params.SPIFFEID, + "exp": jwt.NewNumericDate(time.Now().Add(params.TTL)), + "aud": params.Audience, + "iat": jwt.NewNumericDate(time.Now()), + "iss": "spire", + } + + alg, err := cryptoutil.JoseAlgFromPublicKey(i.jwtKey.Signer.Public()) + if err != nil { + return "", fmt.Errorf("failed to determine JWT key algorithm: %w", err) + } + + jwtSigner, err := jose.NewSigner( + jose.SigningKey{ + Algorithm: alg, + Key: jose.JSONWebKey{ + Key: cryptosigner.Opaque(i.jwtKey.Signer), + KeyID: i.jwtKey.Kid, + }, + }, + new(jose.SignerOptions).WithType("JWT"), + ) + if err != nil { + return "", fmt.Errorf("failed to configure JWT signer: %w", err) + } + + signedToken, err := jwt.Signed(jwtSigner).Claims(claims).Serialize() + if err != nil { + return "", fmt.Errorf("failed to sign JWT SVID: %w", err) + } + + if _, err := i.ValidateWorkloadJWTSVID(signedToken, params.SPIFFEID); err != nil { + return "", err + } + + return signedToken, nil +} + +func (i *InMemoryCA) ValidateWorkloadJWTSVID(rawToken string, id spiffeid.ID) (*jwt.Claims, error) { + token, err := jwt.ParseSigned(rawToken, jwtsvid.AllowedSignatureAlgorithms) + if err != nil { + return nil, fmt.Errorf("failed to parse JWT-SVID for validation: %w", err) + } + + var claims jwt.Claims + if err := token.UnsafeClaimsWithoutVerification(&claims); err != nil { + return nil, fmt.Errorf("failed to extract JWT-SVID claims for validation: %w", err) + } + + now := time.Now() + switch { + case claims.Subject != id.String(): + return nil, fmt.Errorf(`invalid JWT-SVID "sub" claim: expected %q but got %q`, id, claims.Subject) + case claims.Expiry == nil: + return nil, errors.New(`invalid JWT-SVID "exp" claim: required but missing`) + case !claims.Expiry.Time().After(now): + return nil, fmt.Errorf(`invalid JWT-SVID "exp" claim: already expired as of %s`, claims.Expiry.Time().Format(time.RFC3339)) + case claims.NotBefore != nil && claims.NotBefore.Time().After(now): + return nil, fmt.Errorf(`invalid JWT-SVID "nbf" claim: not yet valid until %s`, claims.NotBefore.Time().Format(time.RFC3339)) + case len(claims.Audience) == 0: + return nil, errors.New(`invalid JWT-SVID "aud" claim: required but missing`) + } + return &claims, nil +} + +func (i *InMemoryCA) SignWorkloadJWTSVIDPOP(ctx context.Context, params WorkloadJWTPOParams) (string, error) { + if params.TTL == 0 { + params.TTL = time.Minute * 5 + } + + claims := map[string]any{ + "sub": params.SPIFFEID, + "aud": params.Audience, + "exp": jwt.NewNumericDate(time.Now().Add(params.TTL)), + "iat": jwt.NewNumericDate(time.Now()), + "iss": fmt.Sprintf("spiffe://%s", params.SPIFFEID.TrustDomain()), + "cnf": map[string]any{ + "jwk": params.Key, + }, + } + + claims["jti"] = generateJTI(claims, params.SPIFFEID.String()) + + alg, err := cryptoutil.JoseAlgFromPublicKey(i.jwtKey.Signer.Public()) + if err != nil { + return "", fmt.Errorf("failed to determine JWT key algorithm: %w", err) + } + + jwtSigner, err := jose.NewSigner( + jose.SigningKey{ + Algorithm: alg, + Key: jose.JSONWebKey{ + Key: cryptosigner.Opaque(i.jwtKey.Signer), + KeyID: i.jwtKey.Kid, + }, + }, + new(jose.SignerOptions).WithType("wimse-id+jwt"), + ) + if err != nil { + return "", fmt.Errorf("failed to configure JWT signer: %w", err) + } + + signedToken, err := jwt.Signed(jwtSigner).Claims(claims).Serialize() + if err != nil { + return "", fmt.Errorf("failed to sign JWT SVID: %w", err) + } + + if _, err := i.ValidateWorkloadJWTSVID(signedToken, params.SPIFFEID); err != nil { + return "", err + } + + return signedToken, nil +} + +func generateJTI(claims map[string]any, spiffeID string) string { + // generate a unique identifier for the token using the claims, spiffeID and nonce in SHA256 + + var claimsJSON []byte + var err error + if claimsJSON, err = json.Marshal(claims); err != nil { + return "" + } + + hash := crypto.SHA256.New() + hash.Write([]byte(spiffeID)) + hash.Write(claimsJSON) + + // add 5 bytes of random data to avoid collisions + nonce := make([]byte, 5) + rand.Read(nonce) + hash.Write(nonce) + + return fmt.Sprintf("%x", hash.Sum(nil)) +} diff --git a/internal/pkg/dev/minispire/workload.go b/internal/pkg/dev/minispire/workload.go new file mode 100644 index 0000000..0f97280 --- /dev/null +++ b/internal/pkg/dev/minispire/workload.go @@ -0,0 +1,261 @@ +package minispire + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "errors" + "fmt" + "log" + "net/url" + "time" + + "github.com/cofide/cofide-sdk-go/id" + "github.com/spiffe/go-spiffe/v2/bundle/jwtbundle" + pb "github.com/spiffe/go-spiffe/v2/proto/spiffe/workload" + spiffeid "github.com/spiffe/go-spiffe/v2/spiffeid" + "google.golang.org/grpc/peer" +) + +type Config struct { + CA *InMemoryCA + Domain string +} + +type svidData struct { + certBytes []byte + keyBytes []byte + expiry time.Time +} + +// WorkloadHandler implements the Workload API interface +type WorkloadHandler struct { + c Config + + svids map[string]svidData + + pb.UnimplementedSpiffeWorkloadAPIServer +} + +func NewWorkloadHandler(c Config) *WorkloadHandler { + return &WorkloadHandler{ + c: c, + svids: make(map[string]svidData), + } +} + +func (w *WorkloadHandler) FetchX509SVID(req *pb.X509SVIDRequest, resp pb.SpiffeWorkloadAPI_FetchX509SVIDServer) error { + ctx := resp.Context() + spiffeID, err := w.generateSpiffeID(ctx) + if err != nil { + return err + } + + log.Printf("Issuing SVID for %s", spiffeID.String()) + + if data, ok := w.svids[spiffeID.String()]; ok && time.Now().Add(2*time.Minute).Before(data.expiry) { + err := resp.Send(&pb.X509SVIDResponse{ + Svids: []*pb.X509SVID{ + { + SpiffeId: spiffeID.String(), + X509Svid: data.certBytes, + X509SvidKey: data.keyBytes, + }, + }, + FederatedBundles: map[string][]byte{ + w.c.Domain: w.c.CA.GetCACert(), + }, + }) + if err != nil { + return err + } + + return w.waitForCertUpdateAndSendSVIDs(req, resp, data.expiry) + } + var key crypto.Signer + + if w.c.CA.KeyType == KeyTypeRSA { + var err error + key, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } + } else if w.c.CA.KeyType == KeyTypeECDSAP256 { + var err error + key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + } + + pkcs8Key, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return err + } + + certURL, err := url.Parse(spiffeID.String()) + if err != nil { + return err + } + svidCsr := &x509.CertificateRequest{ + URIs: []*url.URL{certURL}, + } + + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, svidCsr, key) + if err != nil { + return err + } + + svidBytes, notAfter, err := w.c.CA.Sign(csrBytes) + if err != nil { + return err + } + + w.svids[spiffeID.String()] = svidData{ + certBytes: svidBytes, + keyBytes: pkcs8Key, + expiry: notAfter, + } + + err = resp.Send(&pb.X509SVIDResponse{ + Svids: []*pb.X509SVID{ + { + SpiffeId: spiffeID.String(), + X509Svid: svidBytes, + X509SvidKey: pkcs8Key, + }, + }, + FederatedBundles: map[string][]byte{ + w.c.Domain: w.c.CA.GetCACert(), + }, + }) + if err != nil { + return err + } + + return w.waitForCertUpdateAndSendSVIDs(req, resp, notAfter) +} + +func (w *WorkloadHandler) waitForCertUpdateAndSendSVIDs(req *pb.X509SVIDRequest, resp pb.SpiffeWorkloadAPI_FetchX509SVIDServer, expiry time.Time) error { + // this is over simplified logic, however ideal for a local development instance + time.Sleep(time.Until(expiry.Add(-2 * time.Minute))) + + return w.FetchX509SVID(req, resp) +} + +func (w *WorkloadHandler) FetchX509Bundles(req *pb.X509BundlesRequest, resp pb.SpiffeWorkloadAPI_FetchX509BundlesServer) error { + resp.Send(&pb.X509BundlesResponse{ + Bundles: map[string][]byte{ + w.c.Domain: w.c.CA.GetCACert(), + }, + }) + + return nil +} + +func (w *WorkloadHandler) FetchJWTSVID(ctx context.Context, req *pb.JWTSVIDRequest) (*pb.JWTSVIDResponse, error) { + resp := new(pb.JWTSVIDResponse) + + sid, err := w.generateSpiffeID(ctx) + if err != nil { + return nil, err + } + + token, err := w.c.CA.SignWorkloadJWTSVID(ctx, WorkloadJWTSVIDParams{ + SPIFFEID: sid.ToSpiffeID(), + TTL: time.Minute * 5, + Audience: req.Audience, + }) + if err != nil { + return nil, fmt.Errorf("failed to sign JWT SVID: %v", err) + } + + fmt.Printf("JWT SVID issued: %s\n", token) + + resp.Svids = append(resp.Svids, &pb.JWTSVID{ + SpiffeId: sid.String(), + Svid: token, + }) + + return resp, nil +} + +func (w *WorkloadHandler) FetchJWTBundles(req *pb.JWTBundlesRequest, stream pb.SpiffeWorkloadAPI_FetchJWTBundlesServer) error { + ca, err := x509.ParseCertificate(w.c.CA.GetCACert()) + if err != nil { + return err + } + + bundle := jwtbundle.FromJWTAuthorities(spiffeid.RequireTrustDomainFromString(w.c.Domain), map[string]crypto.PublicKey{"kid": ca.PublicKey}) + bundleBytes, err := bundle.Marshal() + if err != nil { + return err + } + + err = stream.Send(&pb.JWTBundlesResponse{ + Bundles: map[string][]byte{ + w.c.Domain: bundleBytes, + }, + }) + if err != nil { + return err + } + return w.waitForCertUpdateAndSendJWTBundle(req, stream) +} + +func (w *WorkloadHandler) waitForCertUpdateAndSendJWTBundle(req *pb.JWTBundlesRequest, stream pb.SpiffeWorkloadAPI_FetchJWTBundlesServer) error { + // this is over simplified logic, however ideal for a local development instance + time.Sleep(time.Minute) + + return w.FetchJWTBundles(req, stream) +} + +func (w *WorkloadHandler) ValidateJWTSVID(ctx context.Context, req *pb.ValidateJWTSVIDRequest) (*pb.ValidateJWTSVIDResponse, error) { + if req.Audience == "" { + return nil, errors.New("audience must be specified") + } + if req.Svid == "" { + return nil, errors.New("svid must be specified") + } + svid, err := spiffeid.FromString(req.Svid) + if err != nil { + return nil, fmt.Errorf("failed to parse SPIFFE ID: %v", err) + } + claims, err := w.c.CA.ValidateWorkloadJWTSVID(req.String(), svid) + if err != nil { + return nil, fmt.Errorf("failed to validate JWT SVID: %v", err) + } + + if !claims.Audience.Contains(req.Audience) { + return nil, errors.New("audience does not match") + } + + return &pb.ValidateJWTSVIDResponse{ + SpiffeId: svid.String(), + }, nil +} + +func (w *WorkloadHandler) generateSpiffeID(ctx context.Context) (*id.SPIFFEID, error) { + peer, ok := peer.FromContext(ctx) + if !ok { + return nil, errors.New("unable to get peer info") + } + ai, ok := peer.AuthInfo.(AuthInfo) + if !ok { + return nil, errors.New("unable to get auth info") + } + info := map[string]string{ + "uid": fmt.Sprint(ai.Caller.UID), + "pid": fmt.Sprint(ai.Caller.PID), + "gid": fmt.Sprint(ai.Caller.GID), + } + if ai.Caller.BinaryName != "" { // can be empty if the the user mini-spire is running as cannot read the ps data + info["bin"] = ai.Caller.BinaryName + } + + return id.CreateID(w.c.Domain, info) +}