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 62c2774..9114d0a 100644 --- a/cmd/cofidectl/cmd/root.go +++ b/cmd/cofidectl/cmd/root.go @@ -13,6 +13,7 @@ import ( "github.com/cofide/cofidectl/cmd/cofidectl/cmd/apbinding" "github.com/cofide/cofidectl/cmd/cofidectl/cmd/attestationpolicy" + "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" @@ -83,6 +84,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 71bc47f..3049f44 100644 --- a/go.mod +++ b/go.mod @@ -1,31 +1,39 @@ module github.com/cofide/cofidectl -go 1.23.0 +go 1.23.3 toolchain go1.23.4 require ( buf.build/go/protoyaml v0.3.1 - cuelang.org/go v0.10.1 + cuelang.org/go v0.10.0 github.com/cofide/cofide-api-sdk v0.5.0 - github.com/fatih/color v1.18.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-api-sdk v1.11.1 + github.com/spiffe/spire v1.11.0 + github.com/spiffe/spire-api-sdk v1.10.4 github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.69.2 - google.golang.org/protobuf v1.36.1 + google.golang.org/protobuf v1.36.2 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.16.4 + helm.sh/helm/v3 v3.16.1 ) +// 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.36.0-20241127180247-a33202765966.1 // indirect - cel.dev/expr v0.18.0 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.1-20241127180247-a33202765966.1 // indirect + cel.dev/expr v0.19.1 // indirect dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect @@ -35,12 +43,12 @@ require ( github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/briandowns/spinner v1.23.1 - github.com/bufbuild/protovalidate-go v0.8.0 // indirect + github.com/bufbuild/protovalidate-go v0.8.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chzyer/readline v1.5.1 // indirect @@ -52,9 +60,9 @@ require ( github.com/cyphar/filepath-securejoin v0.3.4 // 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 @@ -73,9 +81,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.22.1 // 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 +128,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,24 +146,24 @@ 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 + 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.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.49.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.33.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.7.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gotest.tools/v3 v3.5.0 // indirect diff --git a/go.sum b/go.sum index 0418ca9..7223fb4 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,15 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.0-20241127180247-a33202765966.1 h1:ntAj16eF7AtUyzOOAFk5gvbAO52QmUKPKk7GmsIEORo= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.0-20241127180247-a33202765966.1/go.mod h1:AxRT+qTj5PJCz2nyQzsR/qxAcveW5USRhJTt/edTO5w= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.1-20241127180247-a33202765966.1 h1:v223wh/bhlSHSc0tU9PXRWXHhkw3UWMtth7TmYGfHAQ= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.1-20241127180247-a33202765966.1/go.mod h1:/zlFuuECgFgewxwW6qQKgvMJ07YZkWlVkcSxEhJprJw= buf.build/go/protoyaml v0.3.1 h1:ucyzE7DRnjX+mQ6AH4JzN0Kg50ByHHu+yrSKbgQn2D4= buf.build/go/protoyaml v0.3.1/go.mod h1:0TzNpFQDXhwbkXb/ajLvxIijqbve+vMQvWY/b3/Dzxg= -cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= -cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= 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= cuelabs.dev/go/oci/ociregistry v0.0.0-20240807094312-a32ad29eed79 h1:EceZITBGET3qHneD5xowSTY/YHbNybvMWGh62K2fG/M= cuelabs.dev/go/oci/ociregistry v0.0.0-20240807094312-a32ad29eed79/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg= -cuelang.org/go v0.10.1 h1:vDRRsd/5CICzisZ/13kBmXt3M+9eDl/pI06rrHyhlgA= -cuelang.org/go v0.10.1/go.mod h1:HzlaqqqInHNiqE6slTP6+UtxT9hN6DAzgJgdbNxXvX8= +cuelang.org/go v0.10.0 h1:Y1Pu4wwga5HkXfLFK1sWAYaSWIBdcsr5Cb5AWj2pOuE= +cuelang.org/go v0.10.0/go.mod h1:HzlaqqqInHNiqE6slTP6+UtxT9hN6DAzgJgdbNxXvX8= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -39,9 +39,11 @@ 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= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -58,8 +60,8 @@ github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuP github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= -github.com/bufbuild/protovalidate-go v0.8.0 h1:Xs3kCLCJ4tQiogJ0iOXm+ClKw/KviW3nLAryCGW2I3Y= -github.com/bufbuild/protovalidate-go v0.8.0/go.mod h1:JPWZInGm2y2NBg3vKDKdDIkvDjyLv31J3hLH5GIFc/Q= +github.com/bufbuild/protovalidate-go v0.8.2 h1:sgzXHkHYP6HnAsL2Rd3I1JxkYUyEQUv9awU1PduMxbM= +github.com/bufbuild/protovalidate-go v0.8.2/go.mod h1:K6w8iPNAXBoIivVueSELbUeUl+MmeTQfCDSug85pn3M= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -117,12 +119,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= @@ -150,8 +152,8 @@ github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +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= @@ -165,6 +167,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= @@ -213,12 +217,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.22.1 h1:AfVXx3chM2qwoSbM7Da8g8hX8OVSkBFwX+rz2+PcK40= github.com/google/cel-go v0.22.1/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= -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= @@ -262,7 +266,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 +301,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 +327,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 +343,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.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.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/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -386,8 +393,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= @@ -435,8 +442,10 @@ 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-api-sdk v1.11.1 h1:s5RWwBszfMYsRQTGeB6p93NfYBuwHP0tjHFEj5LHun0= -github.com/spiffe/spire-api-sdk v1.11.1/go.mod h1:4uuhFlN6KBWjACRP3xXwrOTNnvaLp1zJs8Lribtr4fI= +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= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -470,12 +479,14 @@ 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/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.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= @@ -511,18 +522,18 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 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-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 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= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -535,8 +546,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= @@ -569,10 +580,10 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= @@ -587,8 +598,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -599,10 +610,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-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= 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= @@ -625,8 +636,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= +google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -646,8 +657,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.16.4 h1:rBn/h9MACw+QlhxQTjpl8Ifx+VTWaYsw3rguGBYBzr0= -helm.sh/helm/v3 v3.16.4/go.mod h1:k8QPotUt57wWbi90w3LNmg3/MWcLPigVv+0/X4B8BzA= +helm.sh/helm/v3 v3.16.1 h1:cER6tI/8PgUAsaJaQCVBUg3VI9KN4oVaZJgY60RIc0c= +helm.sh/helm/v3 v3.16.1/go.mod h1:r+xBHHP20qJeEqtvBXMf7W35QDJnzY/eiEBzt+TfHps= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= diff --git a/internal/pkg/dev/minispire/grpc-authinfo.all.go b/internal/pkg/dev/minispire/grpc-authinfo.all.go new file mode 100644 index 0000000..0369521 --- /dev/null +++ b/internal/pkg/dev/minispire/grpc-authinfo.all.go @@ -0,0 +1,8 @@ +//go:build !linux + +package minispire + +func getProcessInfo(fd uintptr) (int32, uint32, uint32) { + // This function is not implemented for this platform + return 0, 0, 0 +} diff --git a/internal/pkg/dev/minispire/grpc-authinfo.go b/internal/pkg/dev/minispire/grpc-authinfo.go new file mode 100644 index 0000000..041e137 --- /dev/null +++ b/internal/pkg/dev/minispire/grpc-authinfo.go @@ -0,0 +1,114 @@ +package minispire + +import ( + "context" + "errors" + "fmt" + "log" + "net" + "os" + "path/filepath" + + "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) { + pid, uid, gid := getProcessInfo(fd) + if pid == 0 && uid == 0 && gid == 0 { + return + } + auth.Caller.PID = pid + auth.Caller.UID = uid + auth.Caller.GID = 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/grpc-authinfo.linux.go b/internal/pkg/dev/minispire/grpc-authinfo.linux.go new file mode 100644 index 0000000..e496754 --- /dev/null +++ b/internal/pkg/dev/minispire/grpc-authinfo.linux.go @@ -0,0 +1,17 @@ +//go:build linux + +package minispire + +import ( + "log" + "syscall" +) + +func getProcessInfo(fd uintptr) (int32, uint32, uint32) { + 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 0, 0, 0 + } + return int32(cred.Pid), uint32(cred.Uid), uint32(cred.Gid) +} 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..940c587 --- /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/pkg/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.NewID(w.c.Domain, info) +}