From 456ac0be4c12af44c24fa7ccbcadc08bb73f38dd Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Wed, 24 Jan 2024 13:13:41 -0800 Subject: [PATCH 1/9] (wip) encryption --- .../handlers/users/google_oauth_callback.go | 17 +- cmd/hatchet-admin/cli/keyset.go | 55 ++++ go.mod | 28 +- go.sum | 54 +++ internal/auth/token/token.go | 167 ++++++++++ internal/auth/token/token_test.go | 118 +++++++ internal/config/loader/loader.go | 43 +++ internal/config/server/server.go | 42 +++ internal/encryption/256_aes_gcm.go | 70 ---- internal/encryption/cloudkms.go | 85 +++++ internal/encryption/cloudkms_test.go | 85 +++++ internal/encryption/encryption.go | 31 ++ internal/encryption/envelope.go | 38 +++ internal/encryption/local.go | 307 ++++++++++++++++++ internal/encryption/local_test.go | 79 +++++ internal/encryption/service.go | 21 ++ internal/repository/api_token.go | 25 ++ internal/repository/prisma/api_token.go | 66 ++++ internal/repository/prisma/repository.go | 6 + internal/repository/repository.go | 1 + internal/repository/user.go | 4 +- prisma/schema.prisma | 21 +- 22 files changed, 1280 insertions(+), 83 deletions(-) create mode 100644 cmd/hatchet-admin/cli/keyset.go create mode 100644 internal/auth/token/token.go create mode 100644 internal/auth/token/token_test.go delete mode 100644 internal/encryption/256_aes_gcm.go create mode 100644 internal/encryption/cloudkms.go create mode 100644 internal/encryption/cloudkms_test.go create mode 100644 internal/encryption/encryption.go create mode 100644 internal/encryption/envelope.go create mode 100644 internal/encryption/local.go create mode 100644 internal/encryption/local_test.go create mode 100644 internal/encryption/service.go create mode 100644 internal/repository/api_token.go create mode 100644 internal/repository/prisma/api_token.go diff --git a/api/v1/server/handlers/users/google_oauth_callback.go b/api/v1/server/handlers/users/google_oauth_callback.go index 233d01dba..51a1bb8ad 100644 --- a/api/v1/server/handlers/users/google_oauth_callback.go +++ b/api/v1/server/handlers/users/google_oauth_callback.go @@ -66,11 +66,24 @@ func (u *UserService) upsertGoogleUserFromToken(config *server.ServerConfig, tok expiresAt := tok.Expiry + // use the encryption service to encrypt the access and refresh token + accessTokenEncrypted, err := config.Encryption.Encrypt([]byte(tok.AccessToken), "google_access_token") + + if err != nil { + return nil, fmt.Errorf("failed to encrypt access token: %s", err.Error()) + } + + refreshTokenEncrypted, err := config.Encryption.Encrypt([]byte(tok.RefreshToken), "google_refresh_token") + + if err != nil { + return nil, fmt.Errorf("failed to encrypt refresh token: %s", err.Error()) + } + oauthOpts := &repository.OAuthOpts{ Provider: "google", ProviderUserId: gInfo.Sub, - AccessToken: tok.AccessToken, - RefreshToken: repository.StringPtr(tok.RefreshToken), + AccessToken: accessTokenEncrypted, + RefreshToken: &refreshTokenEncrypted, ExpiresAt: &expiresAt, } diff --git a/cmd/hatchet-admin/cli/keyset.go b/cmd/hatchet-admin/cli/keyset.go new file mode 100644 index 000000000..307a3ab2b --- /dev/null +++ b/cmd/hatchet-admin/cli/keyset.go @@ -0,0 +1,55 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/hatchet-dev/hatchet/internal/encryption" +) + +// keysetCmd seeds the database with initial data +var keysetCmd = &cobra.Command{ + Use: "keyset", + Short: "command for managing keysets.", +} + +var keysetCreateLocalCmd = &cobra.Command{ + Use: "create-local", + Short: "create a new local keyset.", + Run: func(cmd *cobra.Command, args []string) { + var err error + + err = runCreateLocalKeyset() + + if err != nil { + fmt.Printf("Fatal: could not run seed command: %v\n", err) + os.Exit(1) + } + }, +} + +func init() { + rootCmd.AddCommand(keysetCmd) + keysetCmd.AddCommand(keysetCreateLocalCmd) +} + +func runCreateLocalKeyset() error { + masterKeyBytes, privateEc256, publicEc256, err := encryption.GenerateLocalKeys() + + if err != nil { + return err + } + + fmt.Println("Master Key Bytes:") + fmt.Println(string(masterKeyBytes)) + + fmt.Println("Private EC256 Keyset:") + fmt.Println(string(privateEc256)) + + fmt.Println("Public EC256 Keyset:") + fmt.Println(string(publicEc256)) + + return nil +} diff --git a/go.mod b/go.mod index e8858b71f..ae7d4cb30 100644 --- a/go.mod +++ b/go.mod @@ -29,8 +29,11 @@ require ( ) require ( + cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -39,6 +42,10 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect @@ -55,14 +62,19 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/tink-crypto/tink-go v0.0.0-20230613075026-d6de17e3f164 // indirect + github.com/tink-crypto/tink-go-gcpkms v0.0.0-20230602082706-31d0d09ccc8d // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/api v0.157.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect ) require ( @@ -93,15 +105,17 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.8.4 github.com/subosito/gotenv v1.6.0 // indirect + github.com/tink-crypto/tink-go-gcpkms/v2 v2.1.0 + github.com/tink-crypto/tink-go/v2 v2.1.0 golang.org/x/crypto v0.18.0 golang.org/x/net v0.20.0 // indirect golang.org/x/oauth2 v0.16.0 - golang.org/x/sync v0.4.0 + golang.org/x/sync v0.6.0 golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/grpc v1.59.0 - google.golang.org/protobuf v1.31.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect + google.golang.org/grpc v1.60.1 + google.golang.org/protobuf v1.32.0 gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index e14c2ff8a..40efdb607 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,10 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -71,6 +75,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 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/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -118,6 +124,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -140,6 +148,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -152,6 +161,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= @@ -174,11 +184,17 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= @@ -321,6 +337,14 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tink-crypto/tink-go v0.0.0-20230613075026-d6de17e3f164 h1:yhVO0Yhq84FjdcotvFFvDJRNHJ7mO743G12VdcW4Evc= +github.com/tink-crypto/tink-go v0.0.0-20230613075026-d6de17e3f164/go.mod h1:HhtDVdE/PRZFRia834tkmcwuscnaAzda1RJUW9Pr3Rg= +github.com/tink-crypto/tink-go-gcpkms v0.0.0-20230602082706-31d0d09ccc8d h1:+In5BwTMe2nF3FC6LrYqg71jDyaOOMZ4EQBFUhFq23g= +github.com/tink-crypto/tink-go-gcpkms v0.0.0-20230602082706-31d0d09ccc8d/go.mod h1:TXKMH7TDt0h7QXtI9TdYPyly6xZL+ooPpbw30qekmEc= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.1.0 h1:A/2tIdYXqUuVZeWy0Yq/PWKsXgebzMyh5mLbpNEMVUo= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.1.0/go.mod h1:QXPc/i5yUEWWZ4lbe2WOam1kDdrXjGHRjl0Lzo7IQDU= +github.com/tink-crypto/tink-go/v2 v2.1.0 h1:QXFBguwMwTIaU17EgZpEJWsUSc60b1BAGTzBIoMdmok= +github.com/tink-crypto/tink-go/v2 v2.1.0/go.mod h1:y1TnYFt1i2eZVfx4OGc+C+EMp4CoKWAw2VSEuoicHHI= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -332,12 +356,17 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +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/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= @@ -361,6 +390,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U 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.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= @@ -401,6 +431,7 @@ 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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -429,10 +460,12 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R 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.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= @@ -458,8 +491,11 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -498,6 +534,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -507,6 +545,7 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -515,6 +554,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -522,6 +562,7 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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.5.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= @@ -569,6 +610,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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= @@ -592,6 +634,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20= +google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -600,6 +644,8 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -640,8 +686,12 @@ google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2Ky google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -660,6 +710,8 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -674,6 +726,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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/auth/token/token.go b/internal/auth/token/token.go new file mode 100644 index 000000000..b39c06965 --- /dev/null +++ b/internal/auth/token/token.go @@ -0,0 +1,167 @@ +package token + +import ( + "fmt" + "time" + + "github.com/google/uuid" + "github.com/tink-crypto/tink-go/jwt" + + "github.com/hatchet-dev/hatchet/internal/encryption" + "github.com/hatchet-dev/hatchet/internal/repository" +) + +type JWTManager interface { + GenerateTenantToken(tenantId string) (string, error) + ValidateTenantToken(token, tenantId string) error +} + +type TokenOpts struct { + Issuer string + Audience string +} + +type jwtManagerImpl struct { + encryption encryption.EncryptionService + opts *TokenOpts + tokenRepo repository.APITokenRepository +} + +func NewJWTManager(encryption encryption.EncryptionService, tokenRepo repository.APITokenRepository, opts *TokenOpts) JWTManager { + return &jwtManagerImpl{ + encryption: encryption, + opts: opts, + tokenRepo: tokenRepo, + } +} + +func (j *jwtManagerImpl) GenerateTenantToken(tenantId string) (string, error) { + // Retrieve the JWT Signer primitive from privateKeysetHandle. + signer, err := jwt.NewSigner(j.encryption.GetPrivateJWTHandle()) + + if err != nil { + return "", fmt.Errorf("failed to create JWT Signer: %v", err) + } + + tokenId, expiresAt, opts := j.getJWTOptionsForTenant(tenantId) + + rawJWT, err := jwt.NewRawJWT(opts) + + if err != nil { + return "", fmt.Errorf("failed to create raw JWT: %v", err) + } + + token, err := signer.SignAndEncode(rawJWT) + + if err != nil { + return "", fmt.Errorf("failed to sign and encode JWT: %v", err) + } + + // write the token to the database + _, err = j.tokenRepo.CreateAPIToken(&repository.CreateAPITokenOpts{ + ID: tokenId, + ExpiresAt: expiresAt, + TenantId: &tenantId, + }) + + if err != nil { + return "", fmt.Errorf("failed to write token to database: %v", err) + } + + return token, nil +} + +func (j *jwtManagerImpl) ValidateTenantToken(token, tenantId string) error { + // Retrieve the Verifier primitive from publicKeysetHandle. + // TODO: move verifier to the constructor + verifier, err := jwt.NewVerifier(j.encryption.GetPublicJWTHandle()) + + if err != nil { + return fmt.Errorf("failed to create JWT Verifier: %v", err) + } + + // Verify the signed token. For this example, we use a fixed date. Usually, you would + // either not set FixedNow, or set it to the current time. + audience := j.opts.Audience + + validator, err := jwt.NewValidator(&jwt.ValidatorOpts{ + ExpectedAudience: &audience, + ExpectedIssuer: &j.opts.Issuer, + FixedNow: time.Now(), + ExpectIssuedInThePast: true, + }) + + if err != nil { + return fmt.Errorf("failed to create JWT Validator: %v", err) + } + + verifiedJwt, err := verifier.VerifyAndDecode(token, validator) + + if err != nil { + return fmt.Errorf("failed to verify and decode JWT: %v", err) + } + + // Read the token from the database and make sure it's not revoked + if hasTokenId := verifiedJwt.HasStringClaim("token_id"); !hasTokenId { + return fmt.Errorf("token does not have token_id claim") + } + + tokenId, err := verifiedJwt.StringClaim("token_id") + + if err != nil { + return fmt.Errorf("failed to read token_id claim: %v", err) + } + + // read the token from the database + dbToken, err := j.tokenRepo.GetAPITokenById(tokenId) + + if err != nil { + return fmt.Errorf("failed to read token from database: %v", err) + } + + if dbToken.Revoked { + return fmt.Errorf("token has been revoked") + } + + if expiresAt, ok := dbToken.ExpiresAt(); ok && expiresAt.Before(time.Now()) { + return fmt.Errorf("token has expired") + } + + // ensure the subject of the token matches the tenantId + if hasSubject := verifiedJwt.HasSubject(); !hasSubject { + return fmt.Errorf("token does not have subject claim") + } + + subject, err := verifiedJwt.Subject() + + if err != nil { + return fmt.Errorf("failed to read subject claim: %v", err) + } + + if subject != tenantId { + return fmt.Errorf("token subject does not match tenantId") + } + + return nil +} + +func (j *jwtManagerImpl) getJWTOptionsForTenant(tenantId string) (tokenId string, expiresAt time.Time, opts *jwt.RawJWTOptions) { + expiresAt = time.Now().Add(30 * 24 * time.Hour) + iAt := time.Now() + audience := j.opts.Audience + subject := tenantId + issuer := j.opts.Issuer + tokenId = uuid.New().String() + opts = &jwt.RawJWTOptions{ + IssuedAt: &iAt, + Audience: &audience, + Subject: &subject, + ExpiresAt: &expiresAt, + Issuer: &issuer, + CustomClaims: map[string]interface{}{ + "token_id": tokenId, + }, + } + + return +} diff --git a/internal/auth/token/token_test.go b/internal/auth/token/token_test.go new file mode 100644 index 000000000..cd5363e04 --- /dev/null +++ b/internal/auth/token/token_test.go @@ -0,0 +1,118 @@ +//go:build integration + +package token_test + +import ( + "fmt" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + + "github.com/hatchet-dev/hatchet/internal/auth/token" + "github.com/hatchet-dev/hatchet/internal/config/database" + "github.com/hatchet-dev/hatchet/internal/encryption" + "github.com/hatchet-dev/hatchet/internal/repository" + "github.com/hatchet-dev/hatchet/internal/testutils" +) + +func TestCreateTenantToken(t *testing.T) { + testutils.RunTestWithDatabase(t, func(conf *database.Config) error { + jwtManager := getJWTManager(t, conf) + + tenantId := uuid.New().String() + + token, err := jwtManager.GenerateTenantToken(tenantId) + + if err != nil { + t.Fatal(err.Error()) + } + + // validate the token + err = jwtManager.ValidateTenantToken(token, tenantId) + + assert.NoError(t, err) + + return nil + }) +} + +func TestRevokeTenantToken(t *testing.T) { + testutils.RunTestWithDatabase(t, func(conf *database.Config) error { + jwtManager := getJWTManager(t, conf) + + tenantId := uuid.New().String() + + // create the tenant + slugSuffix, err := encryption.GenerateRandomBytes(8) + + if err != nil { + t.Fatal(err.Error()) + } + + _, err = conf.Repository.Tenant().CreateTenant(&repository.CreateTenantOpts{ + ID: &tenantId, + Name: "test-tenant", + Slug: fmt.Sprintf("test-tenant-%s", slugSuffix), + }) + + if err != nil { + t.Fatal(err.Error()) + } + + token, err := jwtManager.GenerateTenantToken(tenantId) + + if err != nil { + t.Fatal(err.Error()) + } + + // validate the token + err = jwtManager.ValidateTenantToken(token, tenantId) + + assert.NoError(t, err) + + // revoke the token + apiTokens, err := conf.Repository.APIToken().ListAPITokensByTenant(tenantId) + + if err != nil { + t.Fatal(err.Error()) + } + + assert.Len(t, apiTokens, 1) + err = conf.Repository.APIToken().RevokeAPIToken(apiTokens[0].ID) + + if err != nil { + t.Fatal(err.Error()) + } + + // validate the token again + err = jwtManager.ValidateTenantToken(token, tenantId) + + assert.Error(t, err) + + return nil + }) +} + +func getJWTManager(t *testing.T, conf *database.Config) token.JWTManager { + t.Helper() + + masterKeyBytes, privateJWTBytes, publicJWTBytes, err := encryption.GenerateLocalKeys() + + if err != nil { + t.Fatal(err.Error()) + } + + encryptionService, err := encryption.NewLocalEncryption(masterKeyBytes, privateJWTBytes, publicJWTBytes) + + if err != nil { + t.Fatal(err.Error()) + } + + tokenRepo := conf.Repository.APIToken() + + return token.NewJWTManager(encryptionService, tokenRepo, &token.TokenOpts{ + Issuer: "hatchet", + Audience: "hatchet", + }) +} diff --git a/internal/config/loader/loader.go b/internal/config/loader/loader.go index 5f7729f30..866fde240 100644 --- a/internal/config/loader/loader.go +++ b/internal/config/loader/loader.go @@ -12,9 +12,11 @@ import ( "github.com/hatchet-dev/hatchet/internal/auth/cookie" "github.com/hatchet-dev/hatchet/internal/auth/oauth" + "github.com/hatchet-dev/hatchet/internal/auth/token" "github.com/hatchet-dev/hatchet/internal/config/database" "github.com/hatchet-dev/hatchet/internal/config/loader/loaderutils" "github.com/hatchet-dev/hatchet/internal/config/server" + "github.com/hatchet-dev/hatchet/internal/encryption" "github.com/hatchet-dev/hatchet/internal/logger" "github.com/hatchet-dev/hatchet/internal/repository/prisma" "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" @@ -173,6 +175,40 @@ func GetServerConfigFromConfigfile(dc *database.Config, cf *server.ServerConfigF return nil, fmt.Errorf("could not create ingestor: %w", err) } + if cf.Encryption.MasterKeyset == "" && !cf.Encryption.CloudKMS.Enabled { + return nil, fmt.Errorf("encryption is required") + } + + if cf.Encryption.MasterKeyset != "" && cf.Encryption.CloudKMS.Enabled { + return nil, fmt.Errorf("cannot use both encryption and cloud kms") + } + + if cf.Encryption.JWT.PublicJWTKeyset == "" || cf.Encryption.JWT.PrivateJWTKeyset == "" { + return nil, fmt.Errorf("jwt encryption is required") + } + + var encryptionSvc encryption.EncryptionService + + if cf.Encryption.MasterKeyset != "" { + encryptionSvc, err = encryption.NewLocalEncryption( + []byte(cf.Encryption.MasterKeyset), + []byte(cf.Encryption.JWT.PrivateJWTKeyset), + []byte(cf.Encryption.JWT.PublicJWTKeyset), + ) + + if err != nil { + return nil, fmt.Errorf("could not create raw keyset encryption service: %w", err) + } + } + + if cf.Encryption.CloudKMS.Enabled { + // encryptionSvc, err = encryption.NewCloudKMSEncryption(cf.Encryption.CloudKMS.KeyURI, []byte(cf.Encryption.CloudKMS.CredentialsJSON)) + + // if err != nil { + // return nil, fmt.Errorf("could not create CloudKMS encryption service: %w", err) + // } + } + auth := server.AuthConfig{ ConfigFile: cf.Auth, } @@ -196,9 +232,16 @@ func GetServerConfigFromConfigfile(dc *database.Config, cf *server.ServerConfigF auth.GoogleOAuthConfig = gClient } + // create a new JWT manager + auth.JWTManager = token.NewJWTManager(encryptionSvc, dc.Repository.APIToken(), &token.TokenOpts{ + Issuer: cf.Runtime.ServerURL, + Audience: cf.Runtime.ServerURL, + }) + return &server.ServerConfig{ Runtime: cf.Runtime, Auth: auth, + Encryption: encryptionSvc, Config: dc, TaskQueue: tq, Services: cf.Services, diff --git a/internal/config/server/server.go b/internal/config/server/server.go index 076df89a2..ad0645d41 100644 --- a/internal/config/server/server.go +++ b/internal/config/server/server.go @@ -8,8 +8,10 @@ import ( "golang.org/x/oauth2" "github.com/hatchet-dev/hatchet/internal/auth/cookie" + "github.com/hatchet-dev/hatchet/internal/auth/token" "github.com/hatchet-dev/hatchet/internal/config/database" "github.com/hatchet-dev/hatchet/internal/config/shared" + "github.com/hatchet-dev/hatchet/internal/encryption" "github.com/hatchet-dev/hatchet/internal/services/ingestor" "github.com/hatchet-dev/hatchet/internal/taskqueue" "github.com/hatchet-dev/hatchet/internal/validator" @@ -18,6 +20,8 @@ import ( type ServerConfigFile struct { Auth ConfigFileAuth `mapstructure:"auth" json:"auth,omitempty"` + Encryption EncryptionConfigFile `mapstructure:"encryption" json:"encryption,omitempty"` + Runtime ConfigFileRuntime `mapstructure:"runtime" json:"runtime,omitempty"` TaskQueue TaskQueueConfigFile `mapstructure:"taskQueue" json:"taskQueue,omitempty"` @@ -49,6 +53,40 @@ type ConfigFileRuntime struct { GRPCInsecure bool `mapstructure:"grpcInsecure" json:"grpcInsecure,omitempty" default:"false"` } +// Encryption options +type EncryptionConfigFile struct { + // MasterKeyset is the raw master keyset for the instance. This should be a base64-encoded JSON string. You must set + // either RawKeyset or cloudKms.enabled. + MasterKeyset string `mapstructure:"masterKeyset" json:"masterKeyset,omitempty"` + + JWT EncryptionConfigFileJWT `mapstructure:"jwt" json:"jwt,omitempty"` + + // CloudKMS is the configuration for Google Cloud KMS. You must set either MasterKeyset or cloudKms.enabled. + CloudKMS EncryptionConfigFileCloudKMS `mapstructure:"cloudKms" json:"cloudKms,omitempty"` +} + +type EncryptionConfigFileJWT struct { + // PublicJWTKeyset is a base64-encoded JSON string containing the public keyset which has been encrypted + // by the master key. + PublicJWTKeyset string `mapstructure:"publicJWTKeyset" json:"publicJWTKeyset,omitempty"` + + // PrivateJWTKeyset is a base64-encoded JSON string containing the private keyset which has been encrypted + // by the master key. + PrivateJWTKeyset string `mapstructure:"privateJWTKeyset" json:"privateJWTKeyset,omitempty"` +} + +type EncryptionConfigFileCloudKMS struct { + // Enabled controls whether the Cloud KMS service is enabled for this Hatchet instance. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty" default:"false"` + + // KeyURI is the URI of the key in Google Cloud KMS. This should be in the format of + // gcp-kms://... + KeyURI string `mapstructure:"keyURI" json:"keyURI,omitempty"` + + // CredentialsJSON is the JSON credentials for the Google Cloud KMS service account. + CredentialsJSON string `mapstructure:"credentialsJSON" json:"credentialsJSON,omitempty"` +} + type ConfigFileAuth struct { // RestrictedEmailDomains sets the restricted email domains for the instance. RestrictedEmailDomains []string `mapstructure:"restrictedEmailDomains" json:"restrictedEmailDomains,omitempty"` @@ -95,6 +133,8 @@ type AuthConfig struct { ConfigFile ConfigFileAuth GoogleOAuthConfig *oauth2.Config + + JWTManager token.JWTManager } type ServerConfig struct { @@ -102,6 +142,8 @@ type ServerConfig struct { Auth AuthConfig + Encryption encryption.EncryptionService + Runtime ConfigFileRuntime Services []string diff --git a/internal/encryption/256_aes_gcm.go b/internal/encryption/256_aes_gcm.go deleted file mode 100644 index 45e2757d6..000000000 --- a/internal/encryption/256_aes_gcm.go +++ /dev/null @@ -1,70 +0,0 @@ -package encryption - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "errors" - "io" -) - -// This file is copied from: https://github.com/gtank/cryptopasta - -// NewEncryptionKey generates a random 256-bit key for Encrypt() and -// Decrypt(). It panics if the source of randomness fails. -func NewEncryptionKey() *[32]byte { - key := [32]byte{} - _, err := io.ReadFull(rand.Reader, key[:]) - if err != nil { - panic(err) - } - return &key -} - -// Encrypt encrypts data using 256-bit AES-GCM. This both hides the content of -// the data and provides a check that it hasn't been altered. Output takes the -// form nonce|ciphertext|tag where '|' indicates concatenation. -func Encrypt(plaintext []byte, key *[32]byte) (ciphertext []byte, err error) { - block, err := aes.NewCipher(key[:]) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - nonce := make([]byte, gcm.NonceSize()) - _, err = io.ReadFull(rand.Reader, nonce) - if err != nil { - return nil, err - } - - return gcm.Seal(nonce, nonce, plaintext, nil), nil -} - -// Decrypt decrypts data using 256-bit AES-GCM. This both hides the content of -// the data and provides a check that it hasn't been altered. Expects input -// form nonce|ciphertext|tag where '|' indicates concatenation. -func Decrypt(ciphertext []byte, key *[32]byte) (plaintext []byte, err error) { - block, err := aes.NewCipher(key[:]) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - if len(ciphertext) < gcm.NonceSize() { - return nil, errors.New("malformed ciphertext") - } - - return gcm.Open(nil, - ciphertext[:gcm.NonceSize()], - ciphertext[gcm.NonceSize():], - nil, - ) -} diff --git a/internal/encryption/cloudkms.go b/internal/encryption/cloudkms.go new file mode 100644 index 000000000..c5b985c4b --- /dev/null +++ b/internal/encryption/cloudkms.go @@ -0,0 +1,85 @@ +package encryption + +import ( + "context" + "fmt" + + "github.com/tink-crypto/tink-go-gcpkms/integration/gcpkms" + "github.com/tink-crypto/tink-go/aead" + "github.com/tink-crypto/tink-go/core/registry" + "github.com/tink-crypto/tink-go/jwt" + "github.com/tink-crypto/tink-go/keyset" + "google.golang.org/api/option" +) + +type cloudkmsEncryptionService struct { + key *aead.KMSEnvelopeAEAD + privateEc256Handle *keyset.Handle + publicEc256Handle *keyset.Handle +} + +// NewCloudKMSEncryption creates a GCP CloudKMS-backed encryption service. +func NewCloudKMSEncryption(keyUri string, credentialsJSON []byte) (*cloudkmsEncryptionService, error) { + client, err := gcpkms.NewClientWithOptions(context.Background(), keyUri, option.WithCredentialsJSON(credentialsJSON)) + + if err != nil { + return nil, err + } + + return newWithClient(client, keyUri) +} + +func newWithClient(client registry.KMSClient, keyUri string) (*cloudkmsEncryptionService, error) { + registry.RegisterKMSClient(client) + + dek := aead.AES128CTRHMACSHA256KeyTemplate() + template, err := aead.CreateKMSEnvelopeAEADKeyTemplate(keyUri, dek) + + if err != nil { + return nil, err + } + + // get the remote KEK from the client + remote, err := client.GetAEAD(keyUri) + + if err != nil { + return nil, err + } + + envelope := aead.NewKMSEnvelopeAEAD2(template, remote) + + if envelope == nil { + return nil, fmt.Errorf("failed to create envelope") + } + + jwtTemplate := jwt.ES256Template() + + jwtHandle, err := keyset.NewHandle(jwtTemplate) + + if err != nil { + return nil, err + } + + _, err = jwt.JWKSetFromPublicKeysetHandle(jwtHandle) + + if err != nil { + return nil, err + } + + return &cloudkmsEncryptionService{ + key: envelope, + // jwtHandle: jwtHandle, + }, nil +} + +func (svc *cloudkmsEncryptionService) Encrypt(plaintext []byte, dataId string) ([]byte, error) { + return encrypt(svc.key, plaintext, dataId) +} + +func (svc *cloudkmsEncryptionService) Decrypt(ciphertext []byte, dataId string) ([]byte, error) { + return decrypt(svc.key, ciphertext, dataId) +} + +// func (svc *cloudkmsEncryptionService) GetJWTHandle() *keyset.Handle { +// return svc.jwtHandle +// } diff --git a/internal/encryption/cloudkms_test.go b/internal/encryption/cloudkms_test.go new file mode 100644 index 000000000..78fd5ef67 --- /dev/null +++ b/internal/encryption/cloudkms_test.go @@ -0,0 +1,85 @@ +package encryption + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tink-crypto/tink-go/testing/fakekms" +) + +var ( + fakeKeyURI = "fake-kms://CM2b3_MDElQKSAowdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRvLnRpbmsuQWVzR2NtS2V5EhIaEIK75t5L-adlUwVhWvRuWUwYARABGM2b3_MDIAE" + fakeCredentialsJSON = []byte(`{}`) +) + +func TestNewCloudKMSEncryptionValid(t *testing.T) { + // Using fake KMS client for testing + client, err := fakekms.NewClient(fakeKeyURI) + assert.NoError(t, err) + + // Create encryption service with valid key URI and credentials + svc, err := newWithClient(client, fakeKeyURI) + assert.NoError(t, err) + assert.NotNil(t, svc) +} + +func TestNewCloudKMSEncryptionInvalidKeyUri(t *testing.T) { + // Create encryption service with invalid key URI + _, err := NewCloudKMSEncryption("invalid-key-uri", fakeCredentialsJSON) + assert.Error(t, err) +} + +func TestNewCloudKMSEncryptionInvalidCredentials(t *testing.T) { + // Create encryption service with invalid credentials + _, err := NewCloudKMSEncryption(fakeKeyURI, []byte("invalid credentials")) + assert.Error(t, err) +} + +func TestEncryptDecryptCloudKMS(t *testing.T) { + // Using fake KMS client for testing + client, err := fakekms.NewClient(fakeKeyURI) + assert.NoError(t, err) + + // Create encryption service with valid key URI and credentials + svc, err := newWithClient(client, fakeKeyURI) + + if err != nil { + t.Fatal(err) + } + + plaintext := []byte("test message") + dataID := "123" + + // Encrypt + ciphertext, err := svc.Encrypt(plaintext, dataID) + assert.NoError(t, err) + + // Decrypt + decryptedText, err := svc.Decrypt(ciphertext, dataID) + assert.NoError(t, err) + + // Check if decrypted text matches original plaintext + assert.Equal(t, plaintext, decryptedText) +} + +func TestEncryptDecryptCloudKMSWithEmptyDataID(t *testing.T) { + // Using fake KMS client for testing + // Using fake KMS client for testing + // Using fake KMS client for testing + client, err := fakekms.NewClient(fakeKeyURI) + assert.NoError(t, err) + + // Create encryption service with valid key URI and credentials + svc, err := newWithClient(client, fakeKeyURI) + + if err != nil { + t.Fatal(err) + } + + plaintext := []byte("test message") + emptyDataID := "" + + // Encrypt with empty data ID + _, err = svc.Encrypt(plaintext, emptyDataID) + assert.Error(t, err) +} diff --git a/internal/encryption/encryption.go b/internal/encryption/encryption.go new file mode 100644 index 000000000..f213588c3 --- /dev/null +++ b/internal/encryption/encryption.go @@ -0,0 +1,31 @@ +package encryption + +import ( + "fmt" + + "github.com/tink-crypto/tink-go/tink" +) + +func encrypt(key tink.AEAD, plaintext []byte, dataId string) ([]byte, error) { + // validate data id is not empty + if len(dataId) == 0 { + return nil, fmt.Errorf("data id cannot be empty") + } + + associatedData := []byte(dataId) + + // encrypt the data + return key.Encrypt(plaintext, associatedData) +} + +func decrypt(key tink.AEAD, ciphertext []byte, dataId string) ([]byte, error) { + // validate data id is not empty + if len(dataId) == 0 { + return nil, fmt.Errorf("data id cannot be empty") + } + + associatedData := []byte(dataId) + + // decrypt the data + return key.Decrypt(ciphertext, associatedData) +} diff --git a/internal/encryption/envelope.go b/internal/encryption/envelope.go new file mode 100644 index 000000000..afbacab4a --- /dev/null +++ b/internal/encryption/envelope.go @@ -0,0 +1,38 @@ +package encryption + +// import ( +// "github.com/tink-crypto/tink-go/aead" +// "github.com/tink-crypto/tink-go/proto/tink_go_proto" +// "github.com/tink-crypto/tink-go/tink" +// ) + +// func getEnvelopeKeyWithRemote(keyUri string) + +// func getHandle(template *tink_go_proto.KeyTemplate, remote tink.AEAD) { +// envelope := aead.NewKMSEnvelopeAEAD2(aead.AES128GCMKeyTemplate(), a) + +// if envelope == nil { +// return nil, fmt.Errorf("failed to create envelope") +// } + +// aead.AES128GCMKeyTemplate() + +// dek := aead.AES128CTRHMACSHA256KeyTemplate() +// template, err := aead.CreateKMSEnvelopeAEADKeyTemplate(keyUri, dek) + +// if err != nil { +// return nil, err +// } + +// handle, err := keyset.NewHandle(template) + +// if err != nil { +// return nil, err +// } + +// a, err := aead.New(handle) + +// if err != nil { +// return nil, err +// } +// } diff --git a/internal/encryption/local.go b/internal/encryption/local.go new file mode 100644 index 000000000..9bf232231 --- /dev/null +++ b/internal/encryption/local.go @@ -0,0 +1,307 @@ +package encryption + +import ( + "bytes" + "encoding/base64" + "fmt" + + "github.com/tink-crypto/tink-go/aead" + "github.com/tink-crypto/tink-go/insecurecleartextkeyset" + "github.com/tink-crypto/tink-go/jwt" + "github.com/tink-crypto/tink-go/keyset" + "github.com/tink-crypto/tink-go/tink" +) + +type localEncryptionService struct { + key *aead.KMSEnvelopeAEAD + privateEc256Handle *keyset.Handle + publicEc256Handle *keyset.Handle +} + +// NewLocalEncryption creates a new local encryption service. keysetBytes is the raw keyset in +// base64-encoded JSON format. This can be generated by calling hatchet-admin keyset create-local. +func NewLocalEncryption(masterKey []byte, privateEc256 []byte, publicEc256 []byte) (*localEncryptionService, error) { + // get the master keyset handle + aes256GcmHandle, err := insecureHandleFromBytes(masterKey) + + if err != nil { + return nil, err + } + + a, err := aead.New(aes256GcmHandle) + + if err != nil { + return nil, err + } + + privateEc256Handle, err := handleFromBytes(privateEc256, a) + + if err != nil { + return nil, err + } + + publicEc256Handle, err := handleFromBytes(publicEc256, a) + + if err != nil { + return nil, err + } + + // // base64-decode bytes + // keysetJsonBytes := make([]byte, base64.RawStdEncoding.DecodedLen(len(keysetBytes))) + // _, err := base64.RawStdEncoding.Decode(keysetJsonBytes, keysetBytes) + + // if err != nil { + // return nil, fmt.Errorf("failed to decode keyset bytes: %w", err) + // } + + // // read keyset + // handle, err := insecurecleartextkeyset.Read(keyset.NewJSONReader(bytes.NewReader(keysetJsonBytes))) + + // if err != nil { + // return nil, fmt.Errorf("failed to read keyset: %w", err) + // } + + // jwtTemplate := jwt.ES256Template() + // jwtHandle, err := keyset.NewHandle(jwtTemplate) + + // if err != nil { + // return nil, err + // } + + // kh, err := keyset.NewHandle(signature.ECDSAP256KeyTemplate()) // Other key templates can also be used. + // if err != nil { + // return nil, fmt.Errorf("failed to create new handle: %w", err) + // } + + // publicKeyset, err := kh.Public() + + // if err != nil { + // return nil, fmt.Errorf("failed to get public keyset: %w", err) + // } + + // _, err = jwt.JWKSetFromPublicKeysetHandle(publicKeyset) + + // if err != nil { + // return nil, fmt.Errorf("failed to create JWK set: %w", err) + // } + + // a, err := aead.New(handle) + + // if err != nil { + // return nil, fmt.Errorf("failed to create AEAD: %w", err) + // } + + envelope := aead.NewKMSEnvelopeAEAD2(aead.AES128GCMKeyTemplate(), a) + + if envelope == nil { + return nil, fmt.Errorf("failed to create envelope") + } + + return &localEncryptionService{ + key: envelope, + privateEc256Handle: privateEc256Handle, + publicEc256Handle: publicEc256Handle, + }, nil +} + +func GenerateLocalKeys() (masterKey []byte, privateEc256 []byte, publicEc256 []byte, err error) { + masterKey, masterHandle, err := generateLocalMasterKey() + + if err != nil { + return nil, nil, nil, err + } + + a, err := aead.New(masterHandle) + + if err != nil { + return nil, nil, nil, err + } + + privateEc256, publicEc256, err = generateJWTKeysets(a) + + if err != nil { + return nil, nil, nil, err + } + + return masterKey, privateEc256, publicEc256, nil +} + +func generateLocalMasterKey() ([]byte, *keyset.Handle, error) { + aeadTemplate := aead.AES256GCMKeyTemplate() + + aes256GcmHandle, err := keyset.NewHandle(aeadTemplate) + + if err != nil { + return nil, nil, fmt.Errorf("failed to create new keyset handle with AES256GCM template: %w", err) + } + + bytes, err := insecureBytesFromHandle(aes256GcmHandle) + + if err != nil { + return nil, nil, fmt.Errorf("failed to get bytes from handle: %w", err) + } + + return bytes, aes256GcmHandle, nil +} + +// generateJWTKeysets creates the keysets for JWT signing and verification encrypted with the +// masterKey. The masterKey can be from a remote KMS service or a local keyset. +func generateJWTKeysets(masterKey tink.AEAD) (privateEc256 []byte, publicEc256 []byte, err error) { + privateHandle, err := keyset.NewHandle(jwt.ES256Template()) + + if err != nil { + err = fmt.Errorf("failed to create new keyset handle with ES256 template: %w", err) + return + } + + privateEc256, err = bytesFromHandle(privateHandle, masterKey) + + if err != nil { + return + } + + publicHandle, err := privateHandle.Public() + + if err != nil { + err = fmt.Errorf("failed to get public keyset: %w", err) + return + } + + publicEc256, err = bytesFromHandle(publicHandle, masterKey) + + if err != nil { + return + } + + return +} + +// // NewLocalKeyset generates a new local keyset and returns it as a base64-encoded JSON string. +// // Note that this uses the insecurecleartextkeyset package. It's recommended to use the CloudKMS +// // service, but this will work for smaller deployments. If the keyset is exposed, all API tokens +// // must be revoked and a new local keyset should be created. +// func NewLocalKeysets() (aes256Gcm []byte, privateEc256 []byte, publicEc256 []byte, err error) { +// // Generate a new keyset handle for the primitive we want to use. +// aeadTemplate := aead.AES256GCMKeyTemplate() + +// aes256GcmHandle, err := keyset.NewHandle(aeadTemplate) + +// if err != nil { +// return +// } + +// if aes256Gcm, err = bytesFromHandle(aes256GcmHandle); err != nil { +// return +// } + +// kh, err := keyset.NewHandle(signature.ECDSAP256KeyTemplate()) + +// if err != nil { +// return +// } + +// if privateEc256, err = bytesFromHandle(kh); err != nil { +// return +// } + +// publicKeyset, err := kh.Public() + +// if err != nil { +// return +// } + +// if publicEc256, err = bytesFromHandle(publicKeyset); err != nil { +// return +// } + +// return +// } + +// bytesFromHandle returns the encrypted keyset in base64-encoded JSON format, encrypted with the +// masterKey +func bytesFromHandle(kh *keyset.Handle, masterKey tink.AEAD) ([]byte, error) { + buf := new(bytes.Buffer) + writer := keyset.NewJSONWriter(buf) + err := kh.Write(writer, masterKey) + + if err != nil { + return nil, fmt.Errorf("failed to write keyset: %w", err) + } + + // base64-encode bytes + keysetBytes := make([]byte, base64.RawStdEncoding.EncodedLen(len(buf.Bytes()))) + base64.RawStdEncoding.Encode(keysetBytes, buf.Bytes()) + + return keysetBytes, nil +} + +// insecureBytesFromHandle returns the raw (unencrypted) keyset in base64-encoded JSON format. +func insecureBytesFromHandle(kh *keyset.Handle) ([]byte, error) { + buf := new(bytes.Buffer) + writer := keyset.NewJSONWriter(buf) + err := insecurecleartextkeyset.Write(kh, writer) + + if err != nil { + return nil, fmt.Errorf("failed to write keyset: %w", err) + } + + // base64-encode bytes + keysetBytes := make([]byte, base64.RawStdEncoding.EncodedLen(len(buf.Bytes()))) + base64.RawStdEncoding.Encode(keysetBytes, buf.Bytes()) + + return keysetBytes, nil +} + +func handleFromBytes(keysetBytes []byte, masterKey tink.AEAD) (*keyset.Handle, error) { + // base64-decode bytes + keysetJsonBytes := make([]byte, base64.RawStdEncoding.DecodedLen(len(keysetBytes))) + _, err := base64.RawStdEncoding.Decode(keysetJsonBytes, keysetBytes) + + if err != nil { + return nil, fmt.Errorf("failed to decode keyset bytes: %w", err) + } + + // read keyset + handle, err := keyset.Read(keyset.NewJSONReader(bytes.NewReader(keysetJsonBytes)), masterKey) + + if err != nil { + return nil, fmt.Errorf("failed to read keyset: %w", err) + } + + return handle, nil +} + +func insecureHandleFromBytes(keysetBytes []byte) (*keyset.Handle, error) { + // base64-decode bytes + keysetJsonBytes := make([]byte, base64.RawStdEncoding.DecodedLen(len(keysetBytes))) + _, err := base64.RawStdEncoding.Decode(keysetJsonBytes, keysetBytes) + + if err != nil { + return nil, fmt.Errorf("failed to decode keyset bytes: %w", err) + } + + // read keyset + handle, err := insecurecleartextkeyset.Read(keyset.NewJSONReader(bytes.NewReader(keysetJsonBytes))) + + if err != nil { + return nil, fmt.Errorf("failed to read keyset: %w", err) + } + + return handle, nil +} + +func (svc *localEncryptionService) Encrypt(plaintext []byte, dataId string) ([]byte, error) { + return encrypt(svc.key, plaintext, dataId) +} + +func (svc *localEncryptionService) Decrypt(ciphertext []byte, dataId string) ([]byte, error) { + return decrypt(svc.key, ciphertext, dataId) +} + +func (svc *localEncryptionService) GetPrivateJWTHandle() *keyset.Handle { + return svc.privateEc256Handle +} + +func (svc *localEncryptionService) GetPublicJWTHandle() *keyset.Handle { + return svc.publicEc256Handle +} diff --git a/internal/encryption/local_test.go b/internal/encryption/local_test.go new file mode 100644 index 000000000..d7ddf421a --- /dev/null +++ b/internal/encryption/local_test.go @@ -0,0 +1,79 @@ +package encryption + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewLocalEncryptionValidKeyset(t *testing.T) { + // Generate a new keyset + aes256Gcm, privateEc256, publicEc256, err := GenerateLocalKeys() + assert.NoError(t, err) + + // Create encryption service + _, err = NewLocalEncryption(aes256Gcm, privateEc256, publicEc256) + assert.NoError(t, err) +} + +func TestNewLocalEncryptionInvalidKeyset(t *testing.T) { + invalidKeysetBytes := []byte("invalid keyset") + + // Create encryption service with invalid keyset + _, err := NewLocalEncryption(invalidKeysetBytes, invalidKeysetBytes, invalidKeysetBytes) + assert.Error(t, err) +} + +func TestEncryptDecrypt(t *testing.T) { + aes256Gcm, privateEc256, publicEc256, _ := GenerateLocalKeys() + svc, _ := NewLocalEncryption(aes256Gcm, privateEc256, publicEc256) + + plaintext := []byte("test message") + dataID := "123" + + // Encrypt + ciphertext, err := svc.Encrypt(plaintext, dataID) + assert.NoError(t, err) + + // Decrypt + decryptedText, err := svc.Decrypt(ciphertext, dataID) + assert.NoError(t, err) + + // Check if decrypted text matches original plaintext + assert.Equal(t, plaintext, decryptedText) +} + +func TestDecryptWithInvalidKey(t *testing.T) { + aes256Gcm, privateEc256, publicEc256, _ := GenerateLocalKeys() + svc, _ := NewLocalEncryption(aes256Gcm, privateEc256, publicEc256) + + plaintext := []byte("test message") + dataID := "123" + + // Encrypt + ciphertext, _ := svc.Encrypt(plaintext, dataID) + + // Generate a new keyset for decryption + aes256Gcm, privateEc256, publicEc256, _ = GenerateLocalKeys() + newSvc, _ := NewLocalEncryption(aes256Gcm, privateEc256, publicEc256) + + // Attempt to decrypt with a different key + _, err := newSvc.Decrypt(ciphertext, dataID) + assert.Error(t, err) +} + +func TestEncryptDecryptWithEmptyDataID(t *testing.T) { + aes256Gcm, privateEc256, publicEc256, _ := GenerateLocalKeys() + svc, _ := NewLocalEncryption(aes256Gcm, privateEc256, publicEc256) + + plaintext := []byte("test message") + emptyDataID := "" + + // Encrypt with empty data ID + _, err := svc.Encrypt(plaintext, emptyDataID) + assert.Error(t, err) + + // Decrypt with empty data ID + _, err = svc.Decrypt(plaintext, emptyDataID) + assert.Error(t, err) +} diff --git a/internal/encryption/service.go b/internal/encryption/service.go new file mode 100644 index 000000000..51c9986be --- /dev/null +++ b/internal/encryption/service.go @@ -0,0 +1,21 @@ +package encryption + +import "github.com/tink-crypto/tink-go/keyset" + +type EncryptionService interface { + // Encrypt encrypts the given plaintext with the given data id. The data id is used to + // associate the ciphertext with the data in the database. + // For more information, see: https://developers.google.com/tink/client-side-encryption#kms_envelope_aead + Encrypt(plaintext []byte, dataId string) ([]byte, error) + + // Decrypt decrypts the given ciphertext with the given data id. The data id is used to + // associate the ciphertext with the data in the database. + // For more information, see: https://developers.google.com/tink/client-side-encryption#kms_envelope_aead + Decrypt(ciphertext []byte, dataId string) ([]byte, error) + + // GetPrivateJWTHandle returns a private JWT handle. This is used to sign JWTs. + GetPrivateJWTHandle() *keyset.Handle + + // GetPublicJWTHandle returns a public JWT handle. This is used to verify JWTs. + GetPublicJWTHandle() *keyset.Handle +} diff --git a/internal/repository/api_token.go b/internal/repository/api_token.go new file mode 100644 index 000000000..fe907b71b --- /dev/null +++ b/internal/repository/api_token.go @@ -0,0 +1,25 @@ +package repository + +import ( + "time" + + "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" +) + +type CreateAPITokenOpts struct { + // The id of the token + ID string `validate:"required,uuid"` + + // When the token expires + ExpiresAt time.Time + + // (optional) A tenant ID for this API token + TenantId *string `validate:"omitempty,uuid"` +} + +type APITokenRepository interface { + GetAPITokenById(id string) (*db.APITokenModel, error) + CreateAPIToken(opts *CreateAPITokenOpts) (*db.APITokenModel, error) + RevokeAPIToken(id string) error + ListAPITokensByTenant(tenantId string) ([]db.APITokenModel, error) +} diff --git a/internal/repository/prisma/api_token.go b/internal/repository/prisma/api_token.go new file mode 100644 index 000000000..d8505dbef --- /dev/null +++ b/internal/repository/prisma/api_token.go @@ -0,0 +1,66 @@ +package prisma + +import ( + "context" + "time" + + "github.com/hatchet-dev/hatchet/internal/repository" + "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" + "github.com/hatchet-dev/hatchet/internal/validator" +) + +type apiTokenRepository struct { + client *db.PrismaClient + v validator.Validator +} + +func NewAPITokenRepository(client *db.PrismaClient, v validator.Validator) repository.APITokenRepository { + return &apiTokenRepository{ + client: client, + v: v, + } +} + +func (a *apiTokenRepository) GetAPITokenById(id string) (*db.APITokenModel, error) { + return a.client.APIToken.FindUnique( + db.APIToken.ID.Equals(id), + ).Exec(context.Background()) +} + +func (a *apiTokenRepository) CreateAPIToken(opts *repository.CreateAPITokenOpts) (*db.APITokenModel, error) { + if err := a.v.Validate(opts); err != nil { + return nil, err + } + + optionals := []db.APITokenSetParam{ + db.APIToken.ID.Set(opts.ID), + db.APIToken.ExpiresAt.Set(opts.ExpiresAt), + } + + if opts.TenantId != nil { + optionals = append(optionals, db.APIToken.Tenant.Link( + db.Tenant.ID.Equals(*opts.TenantId), + )) + } + + return a.client.APIToken.CreateOne( + optionals..., + ).Exec(context.Background()) +} + +func (a *apiTokenRepository) RevokeAPIToken(id string) error { + _, err := a.client.APIToken.FindUnique( + db.APIToken.ID.Equals(id), + ).Update( + db.APIToken.ExpiresAt.Set(time.Now().Add(-1*time.Second)), + db.APIToken.Revoked.Set(true), + ).Exec(context.Background()) + + return err +} + +func (a *apiTokenRepository) ListAPITokensByTenant(tenantId string) ([]db.APITokenModel, error) { + return a.client.APIToken.FindMany( + db.APIToken.TenantID.Equals(tenantId), + ).Exec(context.Background()) +} diff --git a/internal/repository/prisma/repository.go b/internal/repository/prisma/repository.go index d6370cd1c..99ea6b195 100644 --- a/internal/repository/prisma/repository.go +++ b/internal/repository/prisma/repository.go @@ -10,6 +10,7 @@ import ( ) type prismaRepository struct { + apiToken repository.APITokenRepository event repository.EventRepository tenant repository.TenantRepository tenantInvite repository.TenantInviteRepository @@ -61,6 +62,7 @@ func NewPrismaRepository(client *db.PrismaClient, pool *pgxpool.Pool, fs ...Pris opts.l = &newLogger return &prismaRepository{ + apiToken: NewAPITokenRepository(client, opts.v), event: NewEventRepository(client, pool, opts.v, opts.l), tenant: NewTenantRepository(client, opts.v), tenantInvite: NewTenantInviteRepository(client, opts.v), @@ -77,6 +79,10 @@ func NewPrismaRepository(client *db.PrismaClient, pool *pgxpool.Pool, fs ...Pris } } +func (r *prismaRepository) APIToken() repository.APITokenRepository { + return r.apiToken +} + func (r *prismaRepository) Event() repository.EventRepository { return r.event } diff --git a/internal/repository/repository.go b/internal/repository/repository.go index b517b0633..9be2b2d2f 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -1,6 +1,7 @@ package repository type Repository interface { + APIToken() APITokenRepository Event() EventRepository Tenant() TenantRepository TenantInvite() TenantInviteRepository diff --git a/internal/repository/user.go b/internal/repository/user.go index 6cd408c0d..089a7a248 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -22,8 +22,8 @@ type CreateUserOpts struct { type OAuthOpts struct { Provider string `validate:"required,oneof=google"` ProviderUserId string `validate:"required,min=1"` - AccessToken string `validate:"required,min=1"` - RefreshToken *string // optional + AccessToken []byte `validate:"required,min=1"` + RefreshToken *[]byte // optional ExpiresAt *time.Time // optional } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 93afbd176..ce9fd2850 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -53,10 +53,10 @@ model UserOAuth { providerUserId String // the oauth provider's access token - accessToken String + accessToken Bytes // the oauth provider's refresh token - refreshToken String? + refreshToken Bytes? // the oauth provider's expiry time expiresAt DateTime? @@ -117,6 +117,7 @@ model Tenant { actions Action[] services Service[] invites TenantInviteLink[] + apiTokens APIToken[] } enum TenantMemberRole { @@ -169,6 +170,22 @@ model TenantInviteLink { role TenantMemberRole @default(OWNER) } +model APIToken { + // base fields + id String @id @unique @default(uuid()) @db.Uuid + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + // when it expires + expiresAt DateTime? + + // whether the token has been revoked + revoked Boolean @default(false) + + tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade, onUpdate: Cascade) + tenantId String? @db.Uuid +} + // Event represents an event in the database. model Event { // base fields From ebeed0452f3a375aeed3dc69bf64fbd2ca3f533d Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Thu, 25 Jan 2024 17:57:40 -0800 Subject: [PATCH 2/9] feat: api tokens --- .pre-commit-config.yaml | 7 + CONTRIBUTING.md | 24 +- api-contracts/dispatcher/dispatcher.proto | 40 +- api-contracts/events/events.proto | 21 +- .../openapi/components/schemas/_index.yaml | 8 + .../components/schemas/api_tokens.yaml | 45 ++ api-contracts/openapi/openapi.yaml | 4 + .../openapi/paths/api-tokens/api_tokens.yaml | 111 ++++ api-contracts/workflows/workflows.proto | 23 +- api/v1/server/authn/middleware.go | 67 ++- api/v1/server/authz/middleware.go | 46 ++ api/v1/server/handlers/api-tokens/create.go | 30 ++ api/v1/server/handlers/api-tokens/list.go | 32 ++ api/v1/server/handlers/api-tokens/revoke.go | 20 + api/v1/server/handlers/api-tokens/service.go | 15 + api/v1/server/oas/gen/openapi.gen.go | 447 +++++++++++++--- api/v1/server/oas/transformers/api_token.go | 22 + api/v1/server/oas/transformers/worker.go | 2 +- api/v1/server/run/run.go | 22 + cmd/hatchet-admin/cli/keyset.go | 105 +++- cmd/hatchet-admin/cli/quickstart.go | 34 ++ cmd/hatchet-engine/main.go | 1 + examples/simple/main.go | 5 +- frontend/app/src/components/ui/code.tsx | 47 +- .../src/components/ui/copy-to-clipboard.tsx | 35 ++ frontend/app/src/lib/api/generated/Api.ts | 55 ++ .../src/lib/api/generated/data-contracts.ts | 32 ++ frontend/app/src/lib/api/queries.ts | 6 + .../components/api-tokens-columns.tsx | 55 ++ .../components/create-token-dialog.tsx | 103 ++++ .../components/invites-columns.tsx | 1 - .../components/revoke-token-form.tsx | 50 ++ .../src/pages/main/tenant-settings/index.tsx | 141 ++++++ go.mod | 9 +- go.sum | 32 +- internal/auth/token/token.go | 37 +- internal/auth/token/token_test.go | 28 +- internal/config/client/client.go | 8 + internal/config/loader/loader.go | 13 +- internal/config/loader/loaderutils/tls.go | 59 ++- internal/config/server/server.go | 9 + internal/config/shared/shared.go | 3 + internal/encryption/cloudkms.go | 50 +- internal/encryption/cloudkms_test.go | 31 +- internal/encryption/local.go | 86 ---- internal/repository/api_token.go | 3 + internal/repository/prisma/api_token.go | 5 + internal/repository/prisma/dbsqlc/models.go | 19 +- internal/repository/prisma/dbsqlc/schema.sql | 35 +- .../repository/prisma/dbsqlc/workflows.sql | 6 +- .../repository/prisma/dbsqlc/workflows.sql.go | 6 +- internal/repository/prisma/worker.go | 22 +- .../services/admin/contracts/workflows.pb.go | 479 ++++++++---------- internal/services/admin/server.go | 28 +- .../dispatcher/contracts/dispatcher.pb.go | 272 +++++----- internal/services/dispatcher/server.go | 39 +- internal/services/grpc/middleware/auth.go | 53 ++ internal/services/grpc/server.go | 26 + .../services/ingestor/contracts/events.pb.go | 110 ++-- internal/services/ingestor/server.go | 12 +- pkg/client/admin.go | 21 +- pkg/client/client.go | 20 +- pkg/client/context.go | 26 + pkg/client/dispatcher.go | 21 +- pkg/client/event.go | 6 +- pkg/client/loader/loader.go | 26 +- prisma/schema.prisma | 15 +- python-client/generate.sh | 6 +- python-client/hatchet_sdk/client.py | 22 +- python-client/hatchet_sdk/clients/admin.py | 22 +- .../hatchet_sdk/clients/dispatcher.py | 29 +- python-client/hatchet_sdk/clients/events.py | 14 +- python-client/hatchet_sdk/dispatcher_pb2.py | 44 +- python-client/hatchet_sdk/dispatcher_pb2.pyi | 24 +- .../hatchet_sdk/dispatcher_pb2_grpc.py | 3 +- python-client/hatchet_sdk/events_pb2.py | 20 +- python-client/hatchet_sdk/events_pb2.pyi | 18 +- python-client/hatchet_sdk/events_pb2_grpc.py | 3 +- python-client/hatchet_sdk/loader.py | 27 +- python-client/hatchet_sdk/metadata.py | 2 + python-client/hatchet_sdk/worker.py | 1 - python-client/hatchet_sdk/workflows_pb2.py | 72 +-- python-client/hatchet_sdk/workflows_pb2.pyi | 36 +- .../hatchet_sdk/workflows_pb2_grpc.py | 4 +- 84 files changed, 2499 insertions(+), 1119 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 api-contracts/openapi/components/schemas/api_tokens.yaml create mode 100644 api-contracts/openapi/paths/api-tokens/api_tokens.yaml create mode 100644 api/v1/server/handlers/api-tokens/create.go create mode 100644 api/v1/server/handlers/api-tokens/list.go create mode 100644 api/v1/server/handlers/api-tokens/revoke.go create mode 100644 api/v1/server/handlers/api-tokens/service.go create mode 100644 api/v1/server/oas/transformers/api_token.go create mode 100644 frontend/app/src/components/ui/copy-to-clipboard.tsx create mode 100644 frontend/app/src/pages/main/tenant-settings/components/api-tokens-columns.tsx create mode 100644 frontend/app/src/pages/main/tenant-settings/components/create-token-dialog.tsx create mode 100644 frontend/app/src/pages/main/tenant-settings/components/revoke-token-form.tsx create mode 100644 internal/services/grpc/middleware/auth.go create mode 100644 pkg/client/context.go create mode 100644 python-client/hatchet_sdk/metadata.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..f120e642e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/gitguardian/ggshield + rev: v1.23.0 + hooks: + - id: ggshield + language_version: python3 + stages: [commit] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 699b171b3..ad9a30569 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ - `docker-compose` - [`Taskfile`](https://taskfile.dev/installation/) - The following additional devtools: - - `protoc`: `brew install protobuf@25` + - `protoc`: `brew install protobuf@25` - `caddy` and `nss`: `brew install caddy nss` ### Setup @@ -83,3 +83,25 @@ OTEL_EXPORTER_OTLP_HEADERS= # optional OTEL_EXPORTER_OTLP_ENDPOINT= ``` + +### CloudKMS + +CloudKMS can be used to generate master encryption keys: + +``` +gcloud kms keyrings create "development" --location "global" +gcloud kms keys create "development" --location "global" --keyring "development" --purpose "encryption" +gcloud kms keys list --location "global" --keyring "development" +``` + +From the last step, copy the Key URI and set the following environment variable: + +``` +SERVER_ENCRYPTION_CLOUDKMS_KEY_URI=gcp-kms://projects//locations/global/keyRings/development/cryptoKeys/development +``` + +Generate a service account in GCP which can encrypt/decrypt on CloudKMS, then download a service account JSON file and set it via: + +``` +SERVER_ENCRYPTION_CLOUDKMS_CREDENTIALS_JSON='{...}' +``` diff --git a/api-contracts/dispatcher/dispatcher.proto b/api-contracts/dispatcher/dispatcher.proto index 66ede681a..a3985a3b4 100644 --- a/api-contracts/dispatcher/dispatcher.proto +++ b/api-contracts/dispatcher/dispatcher.proto @@ -15,17 +15,14 @@ service Dispatcher { } message WorkerRegisterRequest { - // the tenant id - string tenantId = 1; - // the name of the worker - string workerName = 2; + string workerName = 1; // a list of actions that this worker can run - repeated string actions = 3; + repeated string actions = 2; // (optional) the services for this worker - repeated string services = 4; + repeated string services = 3; } message WorkerRegisterResponse { @@ -74,19 +71,13 @@ message AssignedAction { } message WorkerListenRequest { - // the tenant id - string tenantId = 1; - // the id of the worker - string workerId = 2; + string workerId = 1; } message WorkerUnsubscribeRequest { - // the tenant id to unsubscribe from - string tenantId = 1; - // the id of the worker - string workerId = 2; + string workerId = 1; } message WorkerUnsubscribeResponse { @@ -105,34 +96,31 @@ enum ActionEventType { } message ActionEvent { - // the tenant id - string tenantId = 1; - // the id of the worker - string workerId = 2; + string workerId = 1; // the id of the job - string jobId = 3; + string jobId = 2; // the job run id - string jobRunId = 4; + string jobRunId = 3; // the id of the step - string stepId = 5; + string stepId = 4; // the step run id - string stepRunId = 6; + string stepRunId = 5; // the action id - string actionId = 7; + string actionId = 6; - google.protobuf.Timestamp eventTimestamp = 8; + google.protobuf.Timestamp eventTimestamp = 7; // the step event type - ActionEventType eventType = 9; + ActionEventType eventType = 8; // the event payload - string eventPayload = 10; + string eventPayload = 9; } message ActionEventResponse { diff --git a/api-contracts/events/events.proto b/api-contracts/events/events.proto index fb3173acd..4306518cd 100644 --- a/api-contracts/events/events.proto +++ b/api-contracts/events/events.proto @@ -30,28 +30,22 @@ message Event { } message PushEventRequest { - // the tenant id - string tenantId = 1; - // the key for the event - string key = 2; + string key = 1; // the payload for the event - string payload = 3; + string payload = 2; // when the event was generated - google.protobuf.Timestamp eventTimestamp = 4; + google.protobuf.Timestamp eventTimestamp = 3; } message ListEventRequest { - // (required) the tenant id - string tenantId = 1; - // (optional) the number of events to skip - int32 offset = 2; + int32 offset = 1; // (optional) the key for the event - string key = 3; + string key = 2; } message ListEventResponse { @@ -60,9 +54,6 @@ message ListEventResponse { } message ReplayEventRequest { - // the tenant id - string tenantId = 1; - // the event id to replay - string eventId = 2; + string eventId = 1; } \ No newline at end of file diff --git a/api-contracts/openapi/components/schemas/_index.yaml b/api-contracts/openapi/components/schemas/_index.yaml index 007a212ff..dc9ae6656 100644 --- a/api-contracts/openapi/components/schemas/_index.yaml +++ b/api-contracts/openapi/components/schemas/_index.yaml @@ -108,3 +108,11 @@ WorkerList: $ref: "./worker.yaml#/WorkerList" Worker: $ref: "./worker.yaml#/Worker" +APIToken: + $ref: "./api_tokens.yaml#/APIToken" +CreateAPITokenRequest: + $ref: "./api_tokens.yaml#/CreateAPITokenRequest" +CreateAPITokenResponse: + $ref: "./api_tokens.yaml#/CreateAPITokenResponse" +ListAPITokensResponse: + $ref: "./api_tokens.yaml#/ListAPITokensResponse" diff --git a/api-contracts/openapi/components/schemas/api_tokens.yaml b/api-contracts/openapi/components/schemas/api_tokens.yaml new file mode 100644 index 000000000..e88d569f7 --- /dev/null +++ b/api-contracts/openapi/components/schemas/api_tokens.yaml @@ -0,0 +1,45 @@ +APIToken: + type: object + properties: + metadata: + $ref: "./metadata.yaml#/APIResourceMeta" + name: + type: string + description: The name of the API token. + maxLength: 255 + expiresAt: + type: string + format: date-time + description: When the API token expires. + required: + - metadata + - name + - expiresAt + +CreateAPITokenRequest: + type: object + properties: + name: + type: string + description: A name for the API token. + maxLength: 255 + required: + - name + +CreateAPITokenResponse: + type: object + properties: + token: + type: string + description: The API token. + required: + - token + +ListAPITokensResponse: + properties: + pagination: + $ref: "./metadata.yaml#/PaginationResponse" + rows: + items: + $ref: "#/APIToken" + type: array diff --git a/api-contracts/openapi/openapi.yaml b/api-contracts/openapi/openapi.yaml index 55046f5e3..909c135f2 100644 --- a/api-contracts/openapi/openapi.yaml +++ b/api-contracts/openapi/openapi.yaml @@ -48,6 +48,10 @@ paths: $ref: "./paths/tenant/tenant.yaml#/invites" /api/v1/tenants/{tenant}/invites/{tenant-invite}: $ref: "./paths/tenant/tenant.yaml#/inviteScoped" + /api/v1/tenants/{tenant}/api-tokens: + $ref: "./paths/api-tokens/api_tokens.yaml#/withTenant" + /api/v1/api-tokens/{api-token}: + $ref: "./paths/api-tokens/api_tokens.yaml#/revoke" /api/v1/tenants/{tenant}/events: $ref: "./paths/event/event.yaml#/withTenant" /api/v1/tenants/{tenant}/events/replay: diff --git a/api-contracts/openapi/paths/api-tokens/api_tokens.yaml b/api-contracts/openapi/paths/api-tokens/api_tokens.yaml new file mode 100644 index 000000000..4c13928d5 --- /dev/null +++ b/api-contracts/openapi/paths/api-tokens/api_tokens.yaml @@ -0,0 +1,111 @@ +withTenant: + post: + x-resources: ["tenant"] + description: Create an API token for a tenant + operationId: api-token:create + parameters: + - description: The tenant id + in: path + name: tenant + required: true + schema: + type: string + format: uuid + minLength: 36 + maxLength: 36 + requestBody: + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/CreateAPITokenRequest" + responses: + "200": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/CreateAPITokenResponse" + description: Successfully retrieved the workflows + "400": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: A malformed or bad request + "403": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: Forbidden + summary: Create API Token + tags: + - API Token + get: + x-resources: ["tenant"] + description: List API tokens for a tenant + operationId: api-token:list + parameters: + - description: The tenant id + in: path + name: tenant + required: true + schema: + type: string + format: uuid + minLength: 36 + maxLength: 36 + responses: + "200": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/ListAPITokensResponse" + description: Successfully retrieved the workflows + "400": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: A malformed or bad request + "403": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: Forbidden + summary: List API Tokens + tags: + - API Token +revoke: + post: + x-resources: ["tenant", "api-token"] + description: Revoke an API token for a tenant + operationId: api-token:update:revoke + parameters: + - description: The API token + in: path + name: api-token + required: true + schema: + type: string + format: uuid + minLength: 36 + maxLength: 36 + responses: + "204": + description: Successfully revoked the token + "400": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: A malformed or bad request + "403": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: Forbidden + summary: Revoke API Token + tags: + - API Token diff --git a/api-contracts/workflows/workflows.proto b/api-contracts/workflows/workflows.proto index 45810b16f..3d900d5e5 100644 --- a/api-contracts/workflows/workflows.proto +++ b/api-contracts/workflows/workflows.proto @@ -16,8 +16,7 @@ service WorkflowService { } message PutWorkflowRequest { - string tenant_id = 1; - CreateWorkflowVersionOpts opts = 2; + CreateWorkflowVersionOpts opts = 1; } // CreateWorkflowVersionOpts represents options to create a workflow version. @@ -49,17 +48,14 @@ message CreateWorkflowStepOpts { } // ListWorkflowsRequest is the request for ListWorkflows. -message ListWorkflowsRequest { - string tenant_id = 1; -} +message ListWorkflowsRequest {} message ScheduleWorkflowRequest { - string tenant_id = 1; - string workflow_id = 2; - repeated google.protobuf.Timestamp schedules = 3; + string workflow_id = 1; + repeated google.protobuf.Timestamp schedules = 2; // (optional) the input data for the workflow - string input = 4; + string input = 3; } // ListWorkflowsResponse is the response for ListWorkflows. @@ -69,8 +65,7 @@ message ListWorkflowsResponse { // ListWorkflowsForEventRequest is the request for ListWorkflowsForEvent. message ListWorkflowsForEventRequest { - string tenant_id = 1; - string event_key = 2; + string event_key = 1; } // Workflow represents the Workflow model. @@ -147,11 +142,9 @@ message Step { } message DeleteWorkflowRequest { - string tenant_id = 1; - string workflow_id = 2; + string workflow_id = 1; } message GetWorkflowByNameRequest { - string tenant_id = 1; - string name = 2; + string name = 1; } \ No newline at end of file diff --git a/api/v1/server/authn/middleware.go b/api/v1/server/authn/middleware.go index fc9b554ac..3f9472982 100644 --- a/api/v1/server/authn/middleware.go +++ b/api/v1/server/authn/middleware.go @@ -1,13 +1,16 @@ package authn import ( + "fmt" "net/http" + "strings" "github.com/labstack/echo/v4" "github.com/rs/zerolog" "github.com/hatchet-dev/hatchet/api/v1/server/middleware" "github.com/hatchet-dev/hatchet/internal/config/server" + "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" ) type AuthN struct { @@ -51,14 +54,20 @@ func (a *AuthN) authenticate(c echo.Context, r *middleware.RouteInfo) error { if r.Security.CookieAuth() { err = a.handleCookieAuth(c) + c.Set("auth_strategy", "cookie") } - if err != nil { + if err != nil && !r.Security.BearerAuth() { return err } if err != nil && r.Security.BearerAuth() { err = a.handleBearerAuth(c) + c.Set("auth_strategy", "bearer") + + if err == nil { + return nil + } } return err @@ -136,5 +145,59 @@ func (a *AuthN) handleCookieAuth(c echo.Context) error { } func (a *AuthN) handleBearerAuth(c echo.Context) error { - panic("implement me") + forbidden := echo.NewHTTPError(http.StatusForbidden, "Please provide valid credentials") + + // a tenant id must exist in the context in order for the bearer auth to succeed, since + // these tokens are tenant-scoped + queriedTenant, ok := c.Get("tenant").(*db.TenantModel) + + if !ok { + a.l.Debug().Msgf("tenant not found in context") + + return forbidden + } + + token, err := getBearerTokenFromRequest(c.Request()) + + if err != nil { + a.l.Debug().Err(err).Msg("error getting bearer token from request") + + return forbidden + } + + // Validate the token. + tenantId, err := a.config.Auth.JWTManager.ValidateTenantToken(token) + + if err != nil { + a.l.Debug().Err(err).Msg("error validating tenant token") + + return forbidden + } + + // Verify that the tenant id which exists in the context is the same as the tenant id + // in the token. + if queriedTenant.ID != tenantId { + a.l.Debug().Msgf("tenant id in token does not match tenant id in context") + + return forbidden + } + + return nil +} + +var errInvalidAuthHeader = fmt.Errorf("invalid authorization header in request") + +// getPATFromRequest finds an `Authorization` header of the form `Bearer `, +// and returns the token if it exists. +func getBearerTokenFromRequest(r *http.Request) (string, error) { + reqToken := r.Header.Get("Authorization") + splitToken := strings.Split(reqToken, "Bearer") + + if len(splitToken) != 2 { + return "", errInvalidAuthHeader + } + + reqToken = strings.TrimSpace(splitToken[1]) + + return reqToken, nil } diff --git a/api/v1/server/authz/middleware.go b/api/v1/server/authz/middleware.go index 4a44454e4..90c8cd085 100644 --- a/api/v1/server/authz/middleware.go +++ b/api/v1/server/authz/middleware.go @@ -41,6 +41,35 @@ func (a *AuthZ) authorize(c echo.Context, r *middleware.RouteInfo) error { return nil } + var err error + var checkedAuthz bool + + if r.Security.CookieAuth() && c.Get("auth_strategy").(string) == "cookie" { + err = a.handleCookieAuth(c, r) + checkedAuthz = true + } + + if err != nil { + return err + } + + if r.Security.BearerAuth() && c.Get("auth_strategy").(string) == "bearer" { + err = a.handleBearerAuth(c, r) + checkedAuthz = true + } + + if err != nil { + return err + } + + if !checkedAuthz { + return echo.NewHTTPError(http.StatusInternalServerError, "No authorization strategy was checked") + } + + return nil +} + +func (a *AuthZ) handleCookieAuth(c echo.Context, r *middleware.RouteInfo) error { unauthorized := echo.NewHTTPError(http.StatusUnauthorized, "Not authorized to view this resource") if err := a.ensureVerifiedEmail(c, r); err != nil { @@ -87,6 +116,23 @@ func (a *AuthZ) authorize(c echo.Context, r *middleware.RouteInfo) error { return nil } +var restrictedWithBearerToken = []string{ + // bearer tokens cannot read, list, or write other bearer tokens + "ApiTokenList", + "ApiTokenCreate", + "ApiTokenUpdateRevoke", +} + +// At the moment, there's no further bearer auth because bearer tokens are admin-scoped +// and we check that the bearer token has access to the tenant in the authn step. +func (a *AuthZ) handleBearerAuth(c echo.Context, r *middleware.RouteInfo) error { + if operationIn(r.OperationID, restrictedWithBearerToken) { + return echo.NewHTTPError(http.StatusUnauthorized, "Not authorized to perform this operation") + } + + return nil +} + var permittedWithUnverifiedEmail = []string{ "UserGetCurrent", "UserUpdateLogout", diff --git a/api/v1/server/handlers/api-tokens/create.go b/api/v1/server/handlers/api-tokens/create.go new file mode 100644 index 000000000..a7f90dede --- /dev/null +++ b/api/v1/server/handlers/api-tokens/create.go @@ -0,0 +1,30 @@ +package apitokens + +import ( + "github.com/labstack/echo/v4" + + "github.com/hatchet-dev/hatchet/api/v1/server/oas/gen" + "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" +) + +func (a *APITokenService) ApiTokenCreate(ctx echo.Context, request gen.ApiTokenCreateRequestObject) (gen.ApiTokenCreateResponseObject, error) { + tenant := ctx.Get("tenant").(*db.TenantModel) + + // validate the request + if apiErrors, err := a.config.Validator.ValidateAPI(request.Body); err != nil { + return nil, err + } else if apiErrors != nil { + return gen.ApiTokenCreate400JSONResponse(*apiErrors), nil + } + + token, err := a.config.Auth.JWTManager.GenerateTenantToken(tenant.ID, request.Body.Name) + + if err != nil { + return nil, err + } + + // This is the only time the token is sent over the API + return gen.ApiTokenCreate200JSONResponse{ + Token: token, + }, nil +} diff --git a/api/v1/server/handlers/api-tokens/list.go b/api/v1/server/handlers/api-tokens/list.go new file mode 100644 index 000000000..42bb5f171 --- /dev/null +++ b/api/v1/server/handlers/api-tokens/list.go @@ -0,0 +1,32 @@ +package apitokens + +import ( + "github.com/labstack/echo/v4" + + "github.com/hatchet-dev/hatchet/api/v1/server/oas/gen" + "github.com/hatchet-dev/hatchet/api/v1/server/oas/transformers" + "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" +) + +func (a *APITokenService) ApiTokenList(ctx echo.Context, request gen.ApiTokenListRequestObject) (gen.ApiTokenListResponseObject, error) { + tenant := ctx.Get("tenant").(*db.TenantModel) + + tokens, err := a.config.Repository.APIToken().ListAPITokensByTenant(tenant.ID) + + if err != nil { + return nil, err + } + + rows := make([]gen.APIToken, len(tokens)) + + for i := range tokens { + rows[i] = *transformers.ToAPIToken(&tokens[i]) + } + + // This is the only time the token is sent over the API + return gen.ApiTokenList200JSONResponse( + gen.ListAPITokensResponse{ + Rows: &rows, + }, + ), nil +} diff --git a/api/v1/server/handlers/api-tokens/revoke.go b/api/v1/server/handlers/api-tokens/revoke.go new file mode 100644 index 000000000..9190d5e7a --- /dev/null +++ b/api/v1/server/handlers/api-tokens/revoke.go @@ -0,0 +1,20 @@ +package apitokens + +import ( + "github.com/labstack/echo/v4" + + "github.com/hatchet-dev/hatchet/api/v1/server/oas/gen" + "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" +) + +func (a *APITokenService) ApiTokenUpdateRevoke(ctx echo.Context, request gen.ApiTokenUpdateRevokeRequestObject) (gen.ApiTokenUpdateRevokeResponseObject, error) { + apiToken := ctx.Get("api-token").(*db.APITokenModel) + + err := a.config.Repository.APIToken().RevokeAPIToken(apiToken.ID) + + if err != nil { + return nil, err + } + // This is the only time the token is sent over the API + return gen.ApiTokenUpdateRevoke204Response{}, nil +} diff --git a/api/v1/server/handlers/api-tokens/service.go b/api/v1/server/handlers/api-tokens/service.go new file mode 100644 index 000000000..dd7580bd8 --- /dev/null +++ b/api/v1/server/handlers/api-tokens/service.go @@ -0,0 +1,15 @@ +package apitokens + +import ( + "github.com/hatchet-dev/hatchet/internal/config/server" +) + +type APITokenService struct { + config *server.ServerConfig +} + +func NewAPITokenService(config *server.ServerConfig) *APITokenService { + return &APITokenService{ + config: config, + } +} diff --git a/api/v1/server/oas/gen/openapi.gen.go b/api/v1/server/oas/gen/openapi.gen.go index d84420fd1..f4a8f5eb1 100644 --- a/api/v1/server/oas/gen/openapi.gen.go +++ b/api/v1/server/oas/gen/openapi.gen.go @@ -116,11 +116,33 @@ type APIResourceMeta struct { UpdatedAt time.Time `json:"updatedAt"` } +// APIToken defines model for APIToken. +type APIToken struct { + // ExpiresAt When the API token expires. + ExpiresAt time.Time `json:"expiresAt"` + Metadata APIResourceMeta `json:"metadata"` + + // Name The name of the API token. + Name string `json:"name"` +} + // AcceptInviteRequest defines model for AcceptInviteRequest. type AcceptInviteRequest struct { Invite string `json:"invite" validate:"required,uuid"` } +// CreateAPITokenRequest defines model for CreateAPITokenRequest. +type CreateAPITokenRequest struct { + // Name A name for the API token. + Name string `json:"name"` +} + +// CreateAPITokenResponse defines model for CreateAPITokenResponse. +type CreateAPITokenResponse struct { + // Token The API token. + Token string `json:"token"` +} + // CreateTenantInviteRequest defines model for CreateTenantInviteRequest. type CreateTenantInviteRequest struct { // Email The email of the user to invite. @@ -231,6 +253,12 @@ type JobRun struct { // JobRunStatus defines model for JobRunStatus. type JobRunStatus string +// ListAPITokensResponse defines model for ListAPITokensResponse. +type ListAPITokensResponse struct { + Pagination *PaginationResponse `json:"pagination,omitempty"` + Rows *[]APIToken `json:"rows,omitempty"` +} + // PaginationResponse defines model for PaginationResponse. type PaginationResponse struct { // CurrentPage the current page @@ -600,6 +628,9 @@ type WorkflowVersionGetDefinitionParams struct { // TenantCreateJSONRequestBody defines body for TenantCreate for application/json ContentType. type TenantCreateJSONRequestBody = CreateTenantRequest +// ApiTokenCreateJSONRequestBody defines body for ApiTokenCreate for application/json ContentType. +type ApiTokenCreateJSONRequestBody = CreateAPITokenRequest + // EventUpdateReplayJSONRequestBody defines body for EventUpdateReplay for application/json ContentType. type EventUpdateReplayJSONRequestBody = ReplayEventRequest @@ -623,6 +654,9 @@ type UserCreateJSONRequestBody = UserRegisterRequest // ServerInterface represents all server handlers. type ServerInterface interface { + // Revoke API Token + // (POST /api/v1/api-tokens/{api-token}) + ApiTokenUpdateRevoke(ctx echo.Context, apiToken openapi_types.UUID) error // Get event data // (GET /api/v1/events/{event}/data) EventDataGet(ctx echo.Context, event openapi_types.UUID) error @@ -632,6 +666,12 @@ type ServerInterface interface { // Create tenant // (POST /api/v1/tenants) TenantCreate(ctx echo.Context) error + // List API Tokens + // (GET /api/v1/tenants/{tenant}/api-tokens) + ApiTokenList(ctx echo.Context, tenant openapi_types.UUID) error + // Create API Token + // (POST /api/v1/tenants/{tenant}/api-tokens) + ApiTokenCreate(ctx echo.Context, tenant openapi_types.UUID) error // List events // (GET /api/v1/tenants/{tenant}/events) EventList(ctx echo.Context, tenant openapi_types.UUID, params EventListParams) error @@ -717,6 +757,26 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// ApiTokenUpdateRevoke converts echo context to params. +func (w *ServerInterfaceWrapper) ApiTokenUpdateRevoke(ctx echo.Context) error { + var err error + // ------------- Path parameter "api-token" ------------- + var apiToken openapi_types.UUID + + err = runtime.BindStyledParameterWithLocation("simple", false, "api-token", runtime.ParamLocationPath, ctx.Param("api-token"), &apiToken) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter api-token: %s", err)) + } + + ctx.Set(BearerAuthScopes, []string{}) + + ctx.Set(CookieAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.ApiTokenUpdateRevoke(ctx, apiToken) + return err +} + // EventDataGet converts echo context to params. func (w *ServerInterfaceWrapper) EventDataGet(ctx echo.Context) error { var err error @@ -759,6 +819,46 @@ func (w *ServerInterfaceWrapper) TenantCreate(ctx echo.Context) error { return err } +// ApiTokenList converts echo context to params. +func (w *ServerInterfaceWrapper) ApiTokenList(ctx echo.Context) error { + var err error + // ------------- Path parameter "tenant" ------------- + var tenant openapi_types.UUID + + err = runtime.BindStyledParameterWithLocation("simple", false, "tenant", runtime.ParamLocationPath, ctx.Param("tenant"), &tenant) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tenant: %s", err)) + } + + ctx.Set(BearerAuthScopes, []string{}) + + ctx.Set(CookieAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.ApiTokenList(ctx, tenant) + return err +} + +// ApiTokenCreate converts echo context to params. +func (w *ServerInterfaceWrapper) ApiTokenCreate(ctx echo.Context) error { + var err error + // ------------- Path parameter "tenant" ------------- + var tenant openapi_types.UUID + + err = runtime.BindStyledParameterWithLocation("simple", false, "tenant", runtime.ParamLocationPath, ctx.Param("tenant"), &tenant) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tenant: %s", err)) + } + + ctx.Set(BearerAuthScopes, []string{}) + + ctx.Set(CookieAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.ApiTokenCreate(ctx, tenant) + return err +} + // EventList converts echo context to params. func (w *ServerInterfaceWrapper) EventList(ctx echo.Context) error { var err error @@ -1336,9 +1436,12 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.POST(baseURL+"/api/v1/api-tokens/:api-token", wrapper.ApiTokenUpdateRevoke) router.GET(baseURL+"/api/v1/events/:event/data", wrapper.EventDataGet) router.GET(baseURL+"/api/v1/meta", wrapper.MetadataGet) router.POST(baseURL+"/api/v1/tenants", wrapper.TenantCreate) + router.GET(baseURL+"/api/v1/tenants/:tenant/api-tokens", wrapper.ApiTokenList) + router.POST(baseURL+"/api/v1/tenants/:tenant/api-tokens", wrapper.ApiTokenCreate) router.GET(baseURL+"/api/v1/tenants/:tenant/events", wrapper.EventList) router.GET(baseURL+"/api/v1/tenants/:tenant/events/keys", wrapper.EventKeyList) router.POST(baseURL+"/api/v1/tenants/:tenant/events/replay", wrapper.EventUpdateReplay) @@ -1368,6 +1471,40 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL } +type ApiTokenUpdateRevokeRequestObject struct { + ApiToken openapi_types.UUID `json:"api-token"` +} + +type ApiTokenUpdateRevokeResponseObject interface { + VisitApiTokenUpdateRevokeResponse(w http.ResponseWriter) error +} + +type ApiTokenUpdateRevoke204Response struct { +} + +func (response ApiTokenUpdateRevoke204Response) VisitApiTokenUpdateRevokeResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type ApiTokenUpdateRevoke400JSONResponse APIErrors + +func (response ApiTokenUpdateRevoke400JSONResponse) VisitApiTokenUpdateRevokeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type ApiTokenUpdateRevoke403JSONResponse APIErrors + +func (response ApiTokenUpdateRevoke403JSONResponse) VisitApiTokenUpdateRevokeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + type EventDataGetRequestObject struct { Event openapi_types.UUID `json:"event"` } @@ -1463,6 +1600,77 @@ func (response TenantCreate403JSONResponse) VisitTenantCreateResponse(w http.Res return json.NewEncoder(w).Encode(response) } +type ApiTokenListRequestObject struct { + Tenant openapi_types.UUID `json:"tenant"` +} + +type ApiTokenListResponseObject interface { + VisitApiTokenListResponse(w http.ResponseWriter) error +} + +type ApiTokenList200JSONResponse ListAPITokensResponse + +func (response ApiTokenList200JSONResponse) VisitApiTokenListResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ApiTokenList400JSONResponse APIErrors + +func (response ApiTokenList400JSONResponse) VisitApiTokenListResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type ApiTokenList403JSONResponse APIErrors + +func (response ApiTokenList403JSONResponse) VisitApiTokenListResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type ApiTokenCreateRequestObject struct { + Tenant openapi_types.UUID `json:"tenant"` + Body *ApiTokenCreateJSONRequestBody +} + +type ApiTokenCreateResponseObject interface { + VisitApiTokenCreateResponse(w http.ResponseWriter) error +} + +type ApiTokenCreate200JSONResponse CreateAPITokenResponse + +func (response ApiTokenCreate200JSONResponse) VisitApiTokenCreateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ApiTokenCreate400JSONResponse APIErrors + +func (response ApiTokenCreate400JSONResponse) VisitApiTokenCreateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type ApiTokenCreate403JSONResponse APIErrors + +func (response ApiTokenCreate403JSONResponse) VisitApiTokenCreateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + type EventListRequestObject struct { Tenant openapi_types.UUID `json:"tenant"` Params EventListParams @@ -2386,12 +2594,18 @@ func (response WorkflowVersionGetDefinition404JSONResponse) VisitWorkflowVersion } type StrictServerInterface interface { + ApiTokenUpdateRevoke(ctx echo.Context, request ApiTokenUpdateRevokeRequestObject) (ApiTokenUpdateRevokeResponseObject, error) + EventDataGet(ctx echo.Context, request EventDataGetRequestObject) (EventDataGetResponseObject, error) MetadataGet(ctx echo.Context, request MetadataGetRequestObject) (MetadataGetResponseObject, error) TenantCreate(ctx echo.Context, request TenantCreateRequestObject) (TenantCreateResponseObject, error) + ApiTokenList(ctx echo.Context, request ApiTokenListRequestObject) (ApiTokenListResponseObject, error) + + ApiTokenCreate(ctx echo.Context, request ApiTokenCreateRequestObject) (ApiTokenCreateResponseObject, error) + EventList(ctx echo.Context, request EventListRequestObject) (EventListResponseObject, error) EventKeyList(ctx echo.Context, request EventKeyListRequestObject) (EventKeyListResponseObject, error) @@ -2456,6 +2670,31 @@ type strictHandler struct { middlewares []StrictMiddlewareFunc } +// ApiTokenUpdateRevoke operation middleware +func (sh *strictHandler) ApiTokenUpdateRevoke(ctx echo.Context, apiToken openapi_types.UUID) error { + var request ApiTokenUpdateRevokeRequestObject + + request.ApiToken = apiToken + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.ApiTokenUpdateRevoke(ctx, request.(ApiTokenUpdateRevokeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ApiTokenUpdateRevoke") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(ApiTokenUpdateRevokeResponseObject); ok { + return validResponse.VisitApiTokenUpdateRevokeResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + // EventDataGet operation middleware func (sh *strictHandler) EventDataGet(ctx echo.Context, event openapi_types.UUID) error { var request EventDataGetRequestObject @@ -2533,6 +2772,62 @@ func (sh *strictHandler) TenantCreate(ctx echo.Context) error { return nil } +// ApiTokenList operation middleware +func (sh *strictHandler) ApiTokenList(ctx echo.Context, tenant openapi_types.UUID) error { + var request ApiTokenListRequestObject + + request.Tenant = tenant + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.ApiTokenList(ctx, request.(ApiTokenListRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ApiTokenList") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(ApiTokenListResponseObject); ok { + return validResponse.VisitApiTokenListResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + +// ApiTokenCreate operation middleware +func (sh *strictHandler) ApiTokenCreate(ctx echo.Context, tenant openapi_types.UUID) error { + var request ApiTokenCreateRequestObject + + request.Tenant = tenant + + var body ApiTokenCreateJSONRequestBody + if err := ctx.Bind(&body); err != nil { + return err + } + request.Body = &body + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.ApiTokenCreate(ctx, request.(ApiTokenCreateRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ApiTokenCreate") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(ApiTokenCreateResponseObject); ok { + return validResponse.VisitApiTokenCreateResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + // EventList operation middleware func (sh *strictHandler) EventList(ctx echo.Context, tenant openapi_types.UUID, params EventListParams) error { var request EventListRequestObject @@ -3215,80 +3510,84 @@ func (sh *strictHandler) WorkflowVersionGetDefinition(ctx echo.Context, workflow // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xd2XPbOJP/V1jcfdit0uFr8mX15rE9+TwbOyk5ntRuypWCyJaEmCIZALTjTel/38JF", - "giZIgjr8yRM9WRZxNLp/faDRoH76QbJIkxhiRv3RT58Gc1gg8fH04+UFIQnhn1OSpEAYBvEkSELgf0Og", - "AcEpw0nsj3zkBRllycL7J2LBHJgHvLcnGvd8+IEWaQT+6PDk4KDnTxOyQMwf+RmO2ZsTv+ezpxT8kY9j", - "BjMg/rJXHr46m/G/N02Ix+aYyjnN6fzTouEDKJoWQCmaQTErZQTHMzFpEtCvEY7vbVPy7z2WeGwOXpgE", - "2QJihiwE9Dw89TDz4AemjJbImWE2zyaDIFkM55JP/RAe9GcbRVMMUVilhtMgHnlsjpgxuYephyhNAowY", - "hN4jZnNBD0rTCAdoEpXE4cdoYWHEsucT+J5hAqE/+lKa+i5vnEy+QcA4jRortAoWyL/HDBbiw78TmPoj", - "/9+GBfaGCnjDHHXLfBpECHqqkKTGraHmChiq0oIyNncggHc+5U2Xy/rRT9VY5RnEKPJjVVw0S9OEcKHw", - "QamXTD1OEcQMBwJGpmC++BNEceD3/FmSzCLgK805WAFJhVU2ssdAk4wEYGdOQIAD5pTZiWd4AQbUiBrL", - "e0TUU11LuDo6ODrqHx71D48/HR2MDt6MTt4O3r59+7++ofwhYtDnA9twj2tAj0POuBIRPQ/H3u3t5bmn", - "hjYJmUyODk/eHvyjf3TyBvonx+i3Pjr6LeyfHP7jzWF4GEyn/wUmUVmG+UoW6Md7iGdcyMdvev4Cx+a/", - "FWqzNFyVexGizFP9N8nCZwojVlUI2STZqkRBACm7jB8wgzF8z4CyKmaweCzUvCu/u/C35//oJyjFfe5M", - "ZhD34QcjqM/QTFDxgCLMl+KP8gX3hBSXFR5Iem3rPROc+QQxittWDQuEo6qcP83BE48kPsHLKBDuLuSs", - "A38Dy5JTi2UlEbTZMrmaK1hMgIx5+4oRFcOpwdq4UssP4UKs7OBPNDeYGGQTXBDLoFE2s0/Kn2x+0p5y", - "0dd8sRVgKS8qiLLx8eIBYgvn7uHJvoZ7eFIxBXjA+w5sJnIBDIVIGvMWn1ay/dw/CMa4Aahofxnayb08", - "LzP8eQCiwpPahTwm5H4aJY/jLL7JFgtEntooEwz9XO32XDA5i3qC2cZC7rRYzpHNHWq+VhfLn5SF4/3H", - "nzcfrr3JEwP6n4P2aIoPnU//3+thQI/xHttUM0UzHCMdQjcx9GPecgw0TWIKvrAyj+6xW76cakCiCd0V", - "KhtI/EBCIL8/nWMCgSYJ4mzBJYcoD8i4qAwtfyYL1f8PHbbrvoXrre16A4gEc2uAV4f3Ci+nCEdQo6Zx", - "xj0BV1XZyiNZTAdmOFG/G0shDjktLQOrZl1GJlkcO4ysmnUZmWZBABC2syNv6D46x8ufycRiOJq2rcJ+", - "GBtXZTW/JZNNG3jtlCtjUgapu7bcMEirylL2B9XdCF5AkjH78tXDtqU/AKE4ia0z1Nv4nCxzgF7unsXS", - "bf75z2QyzmLLpgjFAUSRDuzddi55pzx/Ut9kDIhKoFg2/jGm825Tf5OIbJIoB61sWSO9NUBHgGYRM0Yt", - "OEwZIqzbYihDLKMO6+H2ULZV+B5ncTeIc+F3R3lwD6RZBbos1wiC2kg2HMGznqvrS3kQDZBcCvVac5OL", - "Sbu6jxfX55fX7/yeP769vpafbm7Pzi4uzi/O/Z7/x+nle/Hh7PT67OI9/2zziRZnb+40f/pBRgjE7GuK", - "ZuCPjnp+DD/0f8c9P84W4h/qjw4PuAsrq3aps23Trlp4qcwV5hMfOTkfgxbb4PxxZeRjt5GLdVlzDQlD", - "kemSeVMRSUaYMrn7KdKxBy6+ziL7MfBPv05+YAxphJ5EGFafGOBPL8Oy6XnpTFRz1lRTyLcfwrdXk6R5", - "1Fv1WnMchQTi0vpaCNiSp0kR0WcW7pQQQCGaRFC3jdXP8xwjeNybWOOTjQVANTPU22xjFSUDrg22EqC0", - "QHyja0Gz9nk7FfCsBi+onXOVAArHacaso33LQ8T2aKRov3nkJxmrI3FFpfieQQanUwbEnU//ikhPgbYc", - "6rluYHjbOm11UOWugVxNYOgUieXIyalujMLKbLGGYerT19Obm8t311cX15/8ni//EXHYWmHapzyVWDYl", - "G9i8rpZRXjs73CCmtkSveYCwvZMDbvZ+pJjYQsBP1eMmOYynugycj+DW2QmudDzRlmdW2WUc2hPM+nE9", - "12SL61p4qREEynTudQWUlM5VClmZ2ecW7OxAorQE5eeOg4fcs6QvFdUf83FFNG3KdKMmYT1AuR90cNVr", - "a31LgcgeH7NJhIMmKIjxGk7YTJp3RuhKfqsIfazkpJ3Qh8/XF2Pubc6vLq/9nn91cfX7xdjqSG7FgbTT", - "EexGTj9rZXJLbeBtNd4oDAlQahrxkq3VVqFqy/mDv4DgKbblqj/Pgc2BFJ5hjqj3oJrzbzEpU2DYqkmS", - "RIDiDSWTLclsTPm+uOSX9cI7m8syH+ok8z6Z4Xj1w/nVpLTWWX2KKH1MSI1T00+b2bcCAfm0y7pz/7xF", - "Ha/HMMOU8b+viN1u0WMNSndQWirmdBaaafjoHKf0tfqVip99QZu8DZMnJ7OJ7bPYMtYl42qifPVQxr5y", - "0+kFKPZSIHx9nB73NECEKPsnIMImgNgpa9xXFNOJ2jUKMfOQN9e9B5stBNz6PlKuZWBPcwQQsxvjSMmW", - "NeRtRB5PHB8XxcDFwOsdRLVsR+sBtQOKr5BtrbfQx1ibOUTXB1iDmoPRGvHxJ7YhnFanDlNtytT9GO9F", - "gF7LIe2bLFqPZqtzSK/xE7KaHXVK3w1PfLy/ZMe8nm0TCsPHvTyvMuE0X7Z3eW5lnu5tV7i1zjleWFeF", - "PjpVlH8un1dbyv43n5VHYYj5ElH00ZiOkQwsBMo8qvvyi9z9c5SuIcCtZcPNYrA8I96cyiZ4NgMC4e9P", - "HQb/ZPQyag2U/nVUV8sI61cs/GVU+ijelRd714zeHfGShifopHxbq7+oAYHl5kgSfxRHTzXA4w1ugjmE", - "WWSvSQNdHu1UuKkOsbdzpNwRjnmnJoxx32e5RRdJC7l+cLO297dvOCWFjQuTsDgjHPpTOzIaTiq/4hpm", - "t02oyjCmNSUYX1Vd/aanpfYVdlfzZ3yznWo/VE5yOwyc82ezvkzaXjv7CnP8VcV03dls+JQyl3X0vk5M", - "vs6ROwmfnY3jmB0fWYuzmAGVDhKjRjBsV2X10MkgPBobK9eAT/fpaAA1zZpLpYHu2oV9DjwgZFaxE/RY", - "fmzZfKNH739Or957Yd6wu70rz+NAtP0G5Qsh7BdACQ+CIcgIZk83xY3aCSACRF+8lVdt/ZH6uljgnDFR", - "8REkyT0G3RxzDsmv9DZw5FeuXaMUiwssS7HpmCZ2Jusb7qcfL3lXzESiq/xtLiX/cHAwOBBCTiFGKfZH", - "/vHgcHAgogc2F0sbohQPHw6H0uwPf4q/y6FG0wwsCbl3wORNdH0fCcXFPSGOSxGSckEV95zeicWmiKAF", - "MGGlvlhzp+JWk6g+FHzjdBZck9GaKWu5AZN4KaF4lRLG5Z0oLxKBtGDO0cGBDJhipsJEdZedUzz8psrJ", - "itlbg0hx40vIuLz0mywIgNJpFkVPHgFGMDzIEzbFESGPZc8/2SBFxd15C0Wn3gJFnJsQegnxJij0iDqK", - "EWQcvwwZfyRkgsMQYqma+u6RgKDBGZ1E+qLuO971/B99fddZfK9O43XEf8eH09hfQDPYqacNSl6aoVUO", - "x5ShOIAK8K9UD4n7raFKv3GgG6Zy+7griDLMrj/6cvdc0uYuXMlZ87csSSllGS0n1CJNea2YesiL4dHL", - "QVGWnTz5kU2VtQHKfk/Cp41xyna92cIzozKIJeptBxX7t9wivnR5Sgu81BVDo2rpb26sutgqKesCbBrC", - "ircVW2VD9PCn/LAcFvszq7l6jymjHooiaR7lmRAySsks/lmkoRycc14EZ/fO+QK35Z57DXcpWeLRe5xq", - "yr5nQJ4K0pLplApDbCGl/u5J83QRXmDmTZ5qphSP153xVNyc4TH1PTxRPusURwxI/bS8XWnWdS9QN9Bk", - "nE040abbr0CgcUjiQKIotxVXmj1BgkHcVLylyUad7FAirZVl6t60hYTPogY28cSuo54liXlnu9PUpdve", - "NTyQk4f5hfJGGs6NZt3pKHq/QBgtDFabT+IoNWNouo+fC5/EOajZ4hw8uzimoTBAjt5JWjUHD6VfM/EK", - "nNTWwa950RX/gtl7HbDpgKe85ib1gIhrm/U7EXmtk+9EtDuVHWs0QJYoy06vRg02v2+yXIat2TbpTJKI", - "TIjm28vtnNwdlSRu76pq1FSKfMPOSt7Uoc1Zn0I1S/d7aE3CwLg/84v7qQo/uqWnnnF7n0koeawKFtvy", - "CYVG9DpkxdQEjVjPU2S/qjuqf3djczJP3YvsktM73Ip2rpDZ08DYq6U1wVfojbteOngq/UVf/r+UShwB", - "s9TfnIvvab6pclFl2efVZgHLetVMWz9nx2v3ra3aKxGyy9pbUiQJwgKudWd4ZTkKv4aYfH9heWa5a+qm", - "CbLPXhN22e/WX9h19buZlvJLn6U5aq56R/Zr0VwpkO6a2+T5FvIeYcc9mu5lV3Hjuvt+j1bmx0p7NM3t", - "fTBo26MVWNxMLPiYX9asrctCUaRu/5UT6xV9MO7p/eKaYHCimw4U55n7dF2pXkcB0MC9uoy5Bu45o/sk", - "i+nwp/nvslkbijNqksXtCqEunTgWK+5k/FdacB1pJgdfsdbmF5dWUlvOob3mVjU3501ZfUUhdkNhZQlT", - "bvpMnVyZaOmmu3t3VijG3qFtXi1oJ51wVIIhUXeFnTSheNODiyfbl/btammfeUbN55wBy0U7qJlY3z99", - "qRDCnTLjUs2rCx/WMJSCL3tjWR9DrGEwMwqEDtWr8JuTQOY789VrD8sW8ZYCeQfsTA22RVyJN/h1A5Og", - "eJcwdPgyZNzGKGPzhOD/g1BO/NvLTHwFbJ6EXpwIz5o86h92K26g/Czd4ftyt6xcSXkGN41xIX4LjOVP", - "aQ4DFEUTFNzXwvksWaTy8I4j4wOf3xM6Y0O0TLp+4Fw80wM/g/bxwVF1kvIBr5oxrM44BxSqBGyUBPkb", - "KQoJPDfYy6aLPHpp5TkcGSdeZlLLtRv+tCvLRKfu/FKvVdk2twR13VjVVtNUFN+WS0jyS22tJpSPYJ5i", - "UH+XioiMgtdfqoLIxUG7mja3CqNa7A2R+PHW+qpX+eOu3Q5kZZ8tXcSz/drsUr0IswrsBvTJle8rZRrD", - "Q8nt1kqZenwR8eM/TVXV/Hk3fMk+/rYKlqu/VrQSvuTK9/hqqRXmTFoBX1Eyw3E9rN4nM+rh2EP5+1br", - "QgvxjugtYanyDup6IL3cXiZKZjMIPRzvtzA7tYUpu3WOGte9SpTM1E9INShDkjE3beBD7QhGOSl7kL6e", - "fbZEjytsF8Vrx923QEYnt22Q/RXnWwa4fdLu+yGTRfs90Sp7IpOD7ZAk6kcMmuJV2YI2GtOtvorE9msL", - "uxBYaObts6SvIsTQEGo316o6SNbxAHGp4LEYYllR5Fipo340oKkcRv5Y0qstX1vhAGvH9Gln6tY6lK3p", - "n1ysAlye7Oelas5lak4H+x1Qr85xm8vAXnmly4qHt3v0289tV6z7ateBofmbCy7KoN8p6qQU6g2pr0g3", - "mosv1OIH3uVUOGGacUhA2BMgjhADynIOYepNgQVzCOsqNIoXob4m1c7f3r9aecZD8fL/X1jT+awnLzPr", - "dcK8aZLFYVNdSAHFLduZYVh6iXMXk2O81bmj9TFe6Ly3Q38zO2TIdj2LZOBrb5x20TiZAlrdTj1PM5kv", - "Ev9yxxXPkngC8qDtRUYif+T7y7vl/wcAAP//5UxIGDCbAAA=", + "H4sIAAAAAAAC/+xdW3PbuJL+KyzuPuxWSZbtODlZv3lsT45nYyfly6R2U64URLYkxBSpAUA73pT++xZu", + "JCgCJKiLjzzRk2kRl0bj6wvQDfBnGGXTWZZCymh4/DOk0QSmSDyefL44JyQj/HlGshkQhkG8ibIY+N8Y", + "aETwjOEsDY9DFEQ5Zdk0+Cdi0QRYALx2IAr3QviBprMEwuODo/39XjjKyBSx8DjMccreHYW9kD3PIDwO", + "ccpgDCSc96rN13sz/g9GGQnYBFPZp9ldeFIWfARF0xQoRWMoe6WM4HQsOs0i+i3B6YOtS/57wLKATSCI", + "syifQsqQhYBegEcBZgH8wJTRCjljzCb5cC/KpoOJ5FM/hkf9bKNohCGJ69RwGsSrgE0QMzoPMA0QpVmE", + "EYM4eMJsIuhBs1mCIzRMKtMRpmhqYcS8FxL4K8cE4vD4a6Xr+6JwNvwOEeM0aqzQOlig+B0zmIqHfycw", + "Co/DfxuU2Bso4A0K1M2LbhAh6LlGkmrXQc0lMFSnBeVs4kEAr3zCi87n7tZPVFvVHkQr8rE+XTSfzTLC", + "J4U3SoNsFHCKIGU4EjAyJ+ZrOEQUR2EvHGfZOAE+0oKDNZDUWGUj+xpolpMI7MyJCHDAnDA78QxPwYAa", + "UW0FT4gGqmoFV4f7h4f9g8P+wZvbw/3j/XfHR+/33r9//7+hIfwxYtDnDdtwjx2gxzFnXIWIXoDT4O7u", + "4ixQTZuEDIeHB0fv9//RPzx6B/2jN+htHx2+jftHB/94dxAfRKPRf4FJVJ5jPpIp+vER0jGf5DfveuEU", + "p+a/NWrzWbws9xJEWaDqr5OFCwIjRlVOskmyQ4huswdILRL9Y4YJUNtQv0wgFdrx5PNFwHj1QJXe8573", + "KTAUI4nQFkGtAHrek7qsRtTtBAL+RsLGoG2vOs2Hb9+28bCgraf1ZskMKxOjCGbsIn3EDK7hrxwoq/MT", + "i9eSsx1B2wWkvfBHP0Mz3OcWeQxpH34wgvoMjQUVjyjBfF7C42LEPSEK8xqQJL228Z4KeGnoOEdsn6cT", + "OUvSlK40TaJ9H/roLEsp1AlkGvl1JFXIaiZDtuKm4xZSlLahA6YIJ3ZSxCuN6pwC4b6JnJ06cUtMv+xa", + "jCpLoE0e5WguYToEcs3L1yy2aE411saVjthZlHEmGlkHF8QwaJKP7Z3yN+vvtKf8wSs+2LkV3oooGx/P", + "HyG1cO4Bnu1jeIDnQuqA191bs16WjPEDUFn+IraTe3FWZfiit6t8YedAnjLyMEqyp+s8vcmnU0Se2ygT", + "DP1Sr9ZgHjizjYHc62k5QzbfS/O1Plj+pjo5wX/8cfPpKhg+M6D/2a6ERNNF9/+9GgZ0Gx+xTTRnaIxT", + "pNdrTQz9XJQsdLDQMk/+C4ViOHXvVxO6LVQ2kPiJxEB+ez7DBCJNEqT5lM8cotz751NlSPnCXKj6v+s1", + "oq5b+nnOqjeASDSxriZceK/xcoRwAg4xTXNuCbioylIBydOqG+he+s8gjTktLQ2rYl1aJnmaerSsinVp", + "meZRBBC3s6Mo6N86x8sf2dCiOJr2SIT+MHZJlNb8ng33NuR419qkDGb+0nLDYFYXlqo9qC998RSynNmH", + "r162Df0RCMVZau3BreMLsswGipWBHLrNPv+RDa9zy8IqQmkESaJXkX7LpaJSsVnnLnINiEqgWHaZUkwn", + "3br+LhHZNKMctLKkY/ZWAB0BmifMaLXkMGWIsG6DoQyxnHqMh+tDWVbh+zpPu0GcT353lEcPQJpFoMtw", + "DSeojWTDECzUXF5eqo1ogBSz4Jaam2KatKn7fH51dnH1IeyF13dXV/Lp5u709Pz87Pws7IW/n1x8FA+n", + "J1en5x/5s80mcmdBLwype2X4ot5DsQdjdSAsHZh7CD/DKCcEUvZthsYQHh/2whR+6P/e9MI0n4p/aHh8", + "sM+NblUZVSrb9rRUiWAmt9KLjg+9zKVBi61x/rrW8hu/lstxWbfiMoYS04ngRYXvm2DK5HqtjFbs+1hn", + "C1qvgT/9Ojs/1zBL0LNwHN1bGfztRVzF/0tv1DYHFTSFfMEkvJF6DKHw0+t2doKTmMiNI9+t+g3Zxhki", + "OqTnTwkBFKNhAq6Ft35fbMFDwO2f1aNam8vm6MFtZYxRVEyONjFqAqUG4ktzC5q1ld4qF205eIGzz2Vc", + "PpzOcmZt7Xvh1Lb7T2X59SM/y5mLxCWF4q8ccjgZMSD+fPpX+KYKtFXn1HfJxcu6pNVDlLu6ng5X1st3", + "LJBTUN3oN1bZYnUc1dO3k5ubiw9Xl+dXt2EvlP8Iz3Elx/K22PysqpKNx7lc29Er72e3R8WcW9NmyGNz", + "sY55EZdzmxgjGiubedFI5XIBlbadcbUfjmP7lrh+7eaaLHHlhJdqoRKnWwIllUhQOVfmfnkLdrZga7cC", + "5UXDwV3ucdaXghpe83aFN23O6VpVwmqA8g/NcNFrK31Hgcgan/NhgqMmKIj2GmKCJs1bM+lq/paZ9Gs1", + "T9oIffpydX7Nrc3Z5cVV2Asvzy9/O7+2GpI7ka/hFTReS7zWOSd31AbeVuWN4pgApaYSr+harRXqupy/", + "+BMIHmHb7vqXCbAJkNIyTBANHlVx/ismVQoMXTXMsgRQurG8kxhTvi6u2GU98M7qssoH18x8zMY4XT6d", + "YLlZWim7YIYofcqIw6jpt83sW4KAotu5K1OhKOHi9TWMMWX87ytit5/36EDpFs6WzsTynTRT8dEJntHX", + "aldqdvYFdfImVJ7szDZtX8SS0bUZ5/Dy1Uvp+8pFZxChNJgB4ePj9PhvAySIsn8CImwIiJ2wxnVF2Z1I", + "7aSQsgAFE117b715shtfR8qx7Nm3OSJI2Y0RBLPtGvIyYh9PBLzLXPmy4dVCZy3LUTegtkDwFbKtAR4d", + "eFtP2F+H3PYcoVzH9PE3tia8RqfCvzZh6h54fBGgOzmkbZNF6tF4eQ7pMd4iq9pReQXd8MTb+1NWLDLw", + "1iEwvN2LM1virh52cHFmZZ6ubRe4leIcLyyrQh69Dlx8qUbYLadi1r8rj+IY8yGi5LPRHSM5WAiU+6j+", + "wy/37hdRusIEbmw33ExfK3bEm7eyCR6PgUD823OHxm+NWkZ2hJK/juJqaWH1HIs/jdwkxbvqYO+b0bsl", + "VtKwBJ2Eb2MZIw4QWA5WZelnEXpyAI8XuIkmEOeJPYsOdEK3V6qpCmJvJqTcEY5FpSaMcdtnOWSaSA25", + "unOzsvW3LzglhY0Dk7A4JRz6IzsyGiKV37CD2W0dqjSMkSMF45s6CbDubql9hN3FfIFvtqj2Yy2S26Hh", + "gj/rtWVS99rZV6rjb8qn685mw6ZUuay991V88lVC7iReiI3jlL05tCZnMQMqHWaMGs6wXZTVSy+F8GQs", + "rHwdPl2nowLUNGsuVRq6b5/sM+AOIbNOO0FP1deWxTd6Cv7n5PJjEBcFu+u7aj8eRNsPGL8Qwn4BlHAn", + "GKKcYPZ8Ux44HwIiQPS5dHkSPTxWP5cDnDAmMj6iLHvAoItjziH5k14GHoe1WwnQDIsjN3Ox6Bhldibr", + "CyBOPl/wqpiJja7qr8UshQd7+3v7YpJnkKIZDo/DN3sHe/vCe2ATMbQBmuHB4wH/0xenKungZ/E8F1DL", + "qGVT7hoeswcIUGocSB5lJEAqah2KXolwTvmUhSczLDJuZaxNVpduDJoCE3rra+OpUL7u5z9y0ktGFrSG", + "JgTkukzCqALuZTIb5/ci60j414Jnh/tHdYbc5FEElI7yJHkOiBheLKP4Os/4aH9ful8pU06nujiCtzD4", + "rpLTSqJ9LnNQ+9WLuwZTlPAhQxxkJBiiOCAqjCLIePMyZPyekSGOYz54cYhHnXTS0OETe6tmTm4BfQ3L", + "3+574Y++Pswv3hW4Kqf8njesESwdl8FP8Xc+0PpwDBb0fgAmrxrRZwBRWp7Nq+K2OFv4QYhrK17lSUKB", + "Mgtc5XrjJaG6PsyVpywtk70Af0YwPCoBkBwR87GTgkIKOAQNzpQyIFebDfiXGKpgfwrNYKeBNolFcpE2", + "GjilDKUR1IB/qWpI3G8MVfpKmW6YKiz8tiDKcBzC46/3izNt7iOpedb8rc6knGXqNrzyKD8NUJDCk8vY", + "ytilLKq0DVD2WxY/r41TtisFLDwzcttYpq6zqem/+QbxpROsWuCljvUaeXd/c2XVRVfJuS7BpiGseFvT", + "VTZED37Kh7nhajpV1kdMWen5UT/PUuymeljoIpfTbqKLXl6jjbafteumW/UCie7MdSkCBSIla7v5rPfz", + "XqM2X2IZVej21wH3TdmfxQuR5iqHaUPi5bjlaCdfK8uXEoQlF4XNBqfc0nYaGxqgJJH+eNXaOBaEr8XW", + "9BouzGBZQB/wTFP2Vw7kuSQtG42o8PwtpLiP6zZ3l+ApZsHw2dGleL1qjyfisHGQjYIHeKa81xFOGBB3", + "t7xcpddVb8lpoMlI5/CirdQVnQk08ko8SBQnlMS9NYEgwSBuJO59tVEnK1RIa2WZuhzHQsIXcWwoC8RG", + "rZslmXkxT6euK1f6OHggO4+LW4MaaTgzinWno6z9Avs2QmG12SmOUnPTZmehFj1AxRbv3RofwzQQCsjT", + "Okmt5mGh9F1iv/iCqMKLrvgXzN7JgE0GAmU11ykHRNx00RRz4u9pgApzKis6JEBHmkSjv+5CyXJ/iGOf", + "TocuhGdCNN9ebqvO31BJ4namyhlhE+dE1mus5OFm2hxmKEWzciSaOnaojSPHv7idqvGj257CArd3W9cV", + "i1XDYtsGtu/GXTUMozpoxPpu3855QXdz9EhdJdEliHSwEelcIpSkgbETS2tEqZQbf7n0sFT6h778fy6F", + "OAFmSVk+E7/TYlHlI8qyzqvdBazKVTNt/YIdr922tkqvRMg2S29FkCQIS7i6kkaq8yjsGmLykupqz3LV", + "1E0SZJ2dJGyz3XXfceJrd3M9yy+dvOEpueqrO69FcuWEdJfcJss3lVcvdFyj6Vp2ETduCNqt0ar8WGqN", + "prm9cwZta7QSi+vxBZ+K+y2cicAoSdSFCS2JRsbVBr+4JBic2OU+rCUVWAHQwL26v2IF3HNG90me0sFP", + "8995szSUMWqSp+0Coc7pembHb6X/VxmwizSTg69Yaouz3kuJLefQTnLrklvwpiq+4uxaQyZ/BVN+8ky9", + "TJko6Se7O3NWCsbOoK1fLGgnmfAUggHJU39JKC/H8rFku9S+bU3tM2PUvM8xsGJq9xwd6ys7XsqF8KfM", + "OIf86tyHFRSl4MtOWbp9iBUUZk6B0IH6elDzJpD5mSF1U3RVI95RIB+AnarGNogrcelxNzAJircJQwcv", + "Q8ZdinI2yQj+P4hlx29fpuNLYJMsDtJMWNbsSX+9tzzy+LNy7cHX+3ntDOQC3DTGxfRbYCw/zj+IUJIM", + "UfTghPNpNp3J4B1HxifefyBkxoZouen6iXPxVDe8AO03+4ctp/sj1WNc73ECKFYbsEkWFZd4lTOwqLDn", + "TSdH9dCqfXgyTtz/5uTaDX/blWWiUnd+qZvoNs0tQV03VrXlNJXJt9UUkuIUdasK5S2YUQwablMSkZHw", + "+ktlEPkYaF/V5pdh5MTeAEURzJg76/VEvO8WkJV1NnTyWzZeiyE64oAN6JMj32XKNLqHktutmTJufBHx", + "vcSmrGr+vhu+ZJ1wUwnL9Q88LoUvOfIdvlpyhTmTlsBXko1x6obVx2xMA5wGqLii3uVaiM9qbAhLtc92", + "bPiAsNdaJsnGY4gDnO6WMFu1hKmadY4a37VKko3VVzcbhCHLmZ808Ka2BKOclB1IX886W6LHF7bT8kst", + "/ksgo5LfMsj+VZgNA9zeaff1kMmi3ZpomTWRycF2SBL13acmf1WWoI3KdKN3X9k+ULUNjoVm3m6X9FW4", + "GBpC7epaZQfJPB4gPhk8FkUsM4o8M3XUd5aa0mHk9yVfbfraEgGsLZOnrclb65C2pr9SXQe4jOwXqWre", + "aWpegf0OqFdx3OY0sFee6bJk8HaHfnvcdsm8r3YZGJifqfIRBn0Nu5dQqEvlX5FsNCdfqMHvBRcjYYRp", + "ziEBcU+AOEEMKCs4hGkwAhZNIHZlaJR3x78m0S4+eLRcesZj+b2kX1jSea9HL9PrVcaCUZancVNeSAnF", + "DeuZQVz57kUXlWN8CKOj9jG+gbHTQ38zPWTM7WoaycDXTjlto3IyJ2h5PbW4zWR+e+XrPRc8y8YTkEet", + "L3KShMdhOL+f/38AAAD//1fM3PiCpwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v1/server/oas/transformers/api_token.go b/api/v1/server/oas/transformers/api_token.go new file mode 100644 index 000000000..5a5b11d1c --- /dev/null +++ b/api/v1/server/oas/transformers/api_token.go @@ -0,0 +1,22 @@ +package transformers + +import ( + "github.com/hatchet-dev/hatchet/api/v1/server/oas/gen" + "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" +) + +func ToAPIToken(token *db.APITokenModel) *gen.APIToken { + res := &gen.APIToken{ + Metadata: *toAPIMetadata(token.ID, token.CreatedAt, token.UpdatedAt), + } + + if expiresAt, ok := token.ExpiresAt(); ok { + res.ExpiresAt = expiresAt + } + + if name, ok := token.Name(); ok { + res.Name = name + } + + return res +} diff --git a/api/v1/server/oas/transformers/worker.go b/api/v1/server/oas/transformers/worker.go index 3833d2662..34874db12 100644 --- a/api/v1/server/oas/transformers/worker.go +++ b/api/v1/server/oas/transformers/worker.go @@ -20,7 +20,7 @@ func ToWorker(worker *db.WorkerModel) *gen.Worker { apiActions := make([]string, len(actions)) for i, action := range actions { - apiActions[i] = action.ID + apiActions[i] = action.ActionID } res.Actions = &apiActions diff --git a/api/v1/server/run/run.go b/api/v1/server/run/run.go index 490ee7676..25ded8cef 100644 --- a/api/v1/server/run/run.go +++ b/api/v1/server/run/run.go @@ -9,6 +9,7 @@ import ( "github.com/hatchet-dev/hatchet/api/v1/server/authn" "github.com/hatchet-dev/hatchet/api/v1/server/authz" + apitokens "github.com/hatchet-dev/hatchet/api/v1/server/handlers/api-tokens" "github.com/hatchet-dev/hatchet/api/v1/server/handlers/events" "github.com/hatchet-dev/hatchet/api/v1/server/handlers/metadata" "github.com/hatchet-dev/hatchet/api/v1/server/handlers/tenants" @@ -29,6 +30,7 @@ type apiService struct { *workflows.WorkflowService *workers.WorkerService *metadata.MetadataService + *apitokens.APITokenService } func newAPIService(config *server.ServerConfig) *apiService { @@ -39,6 +41,7 @@ func newAPIService(config *server.ServerConfig) *apiService { WorkflowService: workflows.NewWorkflowService(config), WorkerService: workers.NewWorkerService(config), MetadataService: metadata.NewMetadataService(config), + APITokenService: apitokens.NewAPITokenService(config), } } @@ -75,6 +78,25 @@ func (t *APIServer) Run(ctx context.Context) error { return tenant, "", nil }) + populatorMW.RegisterGetter("api-token", func(config *server.ServerConfig, parentId, id string) (result interface{}, uniqueParentId string, err error) { + apiToken, err := config.Repository.APIToken().GetAPITokenById(id) + + if err != nil { + return nil, "", err + } + + // at the moment, API tokens should have a tenant id, because there are no other types of + // API tokens. If we add other types of API tokens, we'll need to pass in a parent id to query + // for. + tenantId, ok := apiToken.TenantID() + + if !ok { + return nil, "", fmt.Errorf("api token has no tenant id") + } + + return apiToken, tenantId, nil + }) + populatorMW.RegisterGetter("tenant-invite", func(config *server.ServerConfig, parentId, id string) (result interface{}, uniqueParentId string, err error) { tenantInvite, err := config.Repository.TenantInvite().GetTenantInvite(id) diff --git a/cmd/hatchet-admin/cli/keyset.go b/cmd/hatchet-admin/cli/keyset.go index 307a3ab2b..a7ab16be1 100644 --- a/cmd/hatchet-admin/cli/keyset.go +++ b/cmd/hatchet-admin/cli/keyset.go @@ -9,19 +9,54 @@ import ( "github.com/hatchet-dev/hatchet/internal/encryption" ) +var ( + cloudKMSCredentialsPath string + cloudKMSKeyURI string +) + // keysetCmd seeds the database with initial data var keysetCmd = &cobra.Command{ Use: "keyset", Short: "command for managing keysets.", } -var keysetCreateLocalCmd = &cobra.Command{ - Use: "create-local", - Short: "create a new local keyset.", +var keysetCreateMasterCmd = &cobra.Command{ + Use: "create-master", + Short: "create a new local master keyset.", Run: func(cmd *cobra.Command, args []string) { var err error - err = runCreateLocalKeyset() + err = runCreateLocalMasterKeyset() + + if err != nil { + fmt.Printf("Fatal: could not run seed command: %v\n", err) + os.Exit(1) + } + }, +} + +var keysetCreateLocalJWTCmd = &cobra.Command{ + Use: "create-local-jwt", + Short: "create a new local JWT keyset.", + Run: func(cmd *cobra.Command, args []string) { + var err error + + err = runCreateLocalJWTKeyset() + + if err != nil { + fmt.Printf("Fatal: could not run seed command: %v\n", err) + os.Exit(1) + } + }, +} + +var keysetCreateCloudKMSJWTCmd = &cobra.Command{ + Use: "create-cloudkms-jwt", + Short: "create a new JWT keyset encrypted by a remote CloudKMS repository.", + Run: func(cmd *cobra.Command, args []string) { + var err error + + err = runCreateCloudKMSJWTKeyset() if err != nil { fmt.Printf("Fatal: could not run seed command: %v\n", err) @@ -32,11 +67,27 @@ var keysetCreateLocalCmd = &cobra.Command{ func init() { rootCmd.AddCommand(keysetCmd) - keysetCmd.AddCommand(keysetCreateLocalCmd) + keysetCmd.AddCommand(keysetCreateMasterCmd) + keysetCmd.AddCommand(keysetCreateLocalJWTCmd) + keysetCmd.AddCommand(keysetCreateCloudKMSJWTCmd) + + keysetCreateCloudKMSJWTCmd.PersistentFlags().StringVar( + &cloudKMSCredentialsPath, + "credentials", + "", + "path to the JSON credentials file for the CloudKMS repository", + ) + + keysetCreateCloudKMSJWTCmd.PersistentFlags().StringVar( + &cloudKMSKeyURI, + "key-uri", + "", + "URI of the key in the CloudKMS repository", + ) } -func runCreateLocalKeyset() error { - masterKeyBytes, privateEc256, publicEc256, err := encryption.GenerateLocalKeys() +func runCreateLocalMasterKeyset() error { + masterKeyBytes, _, _, err := encryption.GenerateLocalKeys() if err != nil { return err @@ -45,6 +96,46 @@ func runCreateLocalKeyset() error { fmt.Println("Master Key Bytes:") fmt.Println(string(masterKeyBytes)) + return nil +} + +func runCreateLocalJWTKeyset() error { + _, privateEc256, publicEc256, err := encryption.GenerateLocalKeys() + + if err != nil { + return err + } + + fmt.Println("Private EC256 Keyset:") + fmt.Println(string(privateEc256)) + + fmt.Println("Public EC256 Keyset:") + fmt.Println(string(publicEc256)) + + return nil +} + +func runCreateCloudKMSJWTKeyset() error { + if cloudKMSCredentialsPath == "" { + return fmt.Errorf("missing required flag --credentials") + } + + if cloudKMSKeyURI == "" { + return fmt.Errorf("missing required flag --key-uri") + } + + credentials, err := os.ReadFile(cloudKMSCredentialsPath) + + if err != nil { + return err + } + + privateEc256, publicEc256, err := encryption.GenerateJWTKeysetsFromCloudKMS(cloudKMSKeyURI, credentials) + + if err != nil { + return err + } + fmt.Println("Private EC256 Keyset:") fmt.Println(string(privateEc256)) diff --git a/cmd/hatchet-admin/cli/quickstart.go b/cmd/hatchet-admin/cli/quickstart.go index b78bf5788..b0e72011e 100644 --- a/cmd/hatchet-admin/cli/quickstart.go +++ b/cmd/hatchet-admin/cli/quickstart.go @@ -229,6 +229,40 @@ func generateKeys(generated *generatedConfigFiles) error { generated.sc.Auth.Cookie.Secrets = fmt.Sprintf("%s %s", cookieHashKey, cookieBlockKey) } + // if using local keys, generate master key + if !generated.sc.Encryption.CloudKMS.Enabled { + masterKeyBytes, privateEc256, publicEc256, err := encryption.GenerateLocalKeys() + + if err != nil { + return err + } + + if overwrite || (generated.sc.Encryption.MasterKeyset == "") { + generated.sc.Encryption.MasterKeyset = string(masterKeyBytes) + } + + if overwrite || (generated.sc.Encryption.JWT.PublicJWTKeyset == "") || (generated.sc.Encryption.JWT.PrivateJWTKeyset == "") { + generated.sc.Encryption.JWT.PrivateJWTKeyset = string(privateEc256) + generated.sc.Encryption.JWT.PublicJWTKeyset = string(publicEc256) + } + } + + // generate jwt keys + // TODO: clean up this if statement + if generated.sc.Encryption.CloudKMS.Enabled && (overwrite || (generated.sc.Encryption.JWT.PublicJWTKeyset == "") || (generated.sc.Encryption.JWT.PrivateJWTKeyset == "")) { + privateEc256, publicEc256, err := encryption.GenerateJWTKeysetsFromCloudKMS( + generated.sc.Encryption.CloudKMS.KeyURI, + []byte(generated.sc.Encryption.CloudKMS.CredentialsJSON), + ) + + if err != nil { + return err + } + + generated.sc.Encryption.JWT.PrivateJWTKeyset = string(privateEc256) + generated.sc.Encryption.JWT.PublicJWTKeyset = string(publicEc256) + } + return nil } diff --git a/cmd/hatchet-engine/main.go b/cmd/hatchet-engine/main.go index ef6c479ec..87f4b622a 100644 --- a/cmd/hatchet-engine/main.go +++ b/cmd/hatchet-engine/main.go @@ -134,6 +134,7 @@ func startEngineOrDie(cf *loader.ConfigLoader, interruptCh <-chan interface{}) { } grpcOpts := []grpc.ServerOpt{ + grpc.WithConfig(sc), grpc.WithIngestor(ei), grpc.WithDispatcher(d), grpc.WithAdmin(adminSvc), diff --git a/examples/simple/main.go b/examples/simple/main.go index 35621bec0..e6be771d8 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -6,10 +6,11 @@ import ( "log" "time" + "github.com/joho/godotenv" + "github.com/hatchet-dev/hatchet/pkg/client" "github.com/hatchet-dev/hatchet/pkg/cmdutils" "github.com/hatchet-dev/hatchet/pkg/worker" - "github.com/joho/godotenv" ) type userCreateEvent struct { @@ -141,4 +142,6 @@ func run(ch <-chan interface{}, events chan<- string) error { time.Sleep(time.Second) } } + + return nil } diff --git a/frontend/app/src/components/ui/code.tsx b/frontend/app/src/components/ui/code.tsx index 12ffcb955..874637924 100644 --- a/frontend/app/src/components/ui/code.tsx +++ b/frontend/app/src/components/ui/code.tsx @@ -6,6 +6,7 @@ import yaml from 'react-syntax-highlighter/dist/esm/languages/hljs/yaml'; import json from 'react-syntax-highlighter/dist/esm/languages/hljs/json'; import { anOldHope } from 'react-syntax-highlighter/dist/esm/styles/hljs'; +import CopyToClipboard from './copy-to-clipboard'; SyntaxHighlighter.registerLanguage('typescript', typescript); SyntaxHighlighter.registerLanguage('yaml', yaml); @@ -16,18 +17,24 @@ export function Code({ language, className, maxHeight, + maxWidth, + copy, + wrapLines = true, }: { children: string; language: string; className?: string; maxHeight?: string; + maxWidth?: string; + copy?: boolean; + wrapLines?: boolean; }) { return ( -
+
{children.trim()} + {copy && }
); } - -// import { Fragment } from "react"; -// import { Highlight, themes } from "prism-react-renderer"; - -// const theme = themes.nightOwl; - -// export function Code({ -// children, -// language, -// }: { -// children: string; -// language: string; -// }) { -// return ( -// -// {({ className, style, tokens, getTokenProps }) => ( -//
-//           
-//             {tokens.map((line, lineIndex) => (
-//               
-//                 {line
-//                   .filter((token) => !token.empty)
-//                   .map((token, tokenIndex) => (
-//                     
-//                   ))}
-//                 {"\n"}
-//               
-//             ))}
-//           
-//         
-// )} -//
-// ); -// } diff --git a/frontend/app/src/components/ui/copy-to-clipboard.tsx b/frontend/app/src/components/ui/copy-to-clipboard.tsx new file mode 100644 index 000000000..5f3b9b6bf --- /dev/null +++ b/frontend/app/src/components/ui/copy-to-clipboard.tsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react'; +import { Button } from './button'; +import { CopyIcon } from '@radix-ui/react-icons'; +import { CheckCircleIcon } from '@heroicons/react/24/outline'; + +type Props = { + text: string; +}; + +const CopyToClipboard: React.FC = ({ text }) => { + const [successCopy, setSuccessCopy] = useState(false); + + return ( + + ); +}; + +export default CopyToClipboard; diff --git a/frontend/app/src/lib/api/generated/Api.ts b/frontend/app/src/lib/api/generated/Api.ts index e44f607cc..18cf2a9f8 100644 --- a/frontend/app/src/lib/api/generated/Api.ts +++ b/frontend/app/src/lib/api/generated/Api.ts @@ -14,6 +14,8 @@ import { APIErrors, APIMeta, AcceptInviteRequest, + CreateAPITokenRequest, + CreateAPITokenResponse, CreateTenantInviteRequest, CreateTenantRequest, EventData, @@ -23,6 +25,7 @@ import { EventOrderByDirection, EventOrderByField, EventSearch, + ListAPITokensResponse, RejectInviteRequest, ReplayEventRequest, Tenant, @@ -322,6 +325,58 @@ export class Api extends HttpClient + this.request({ + path: `/api/v1/tenants/${tenant}/api-tokens`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params, + }); + /** + * @description List API tokens for a tenant + * + * @tags API Token + * @name ApiTokenList + * @summary List API Tokens + * @request GET:/api/v1/tenants/{tenant}/api-tokens + * @secure + */ + apiTokenList = (tenant: string, params: RequestParams = {}) => + this.request({ + path: `/api/v1/tenants/${tenant}/api-tokens`, + method: "GET", + secure: true, + format: "json", + ...params, + }); + /** + * @description Revoke an API token for a tenant + * + * @tags API Token + * @name ApiTokenUpdateRevoke + * @summary Revoke API Token + * @request POST:/api/v1/api-tokens/{api-token} + * @secure + */ + apiTokenUpdateRevoke = (apiToken: string, params: RequestParams = {}) => + this.request({ + path: `/api/v1/api-tokens/${apiToken}`, + method: "POST", + secure: true, + ...params, + }); /** * @description Lists all events for a tenant. * diff --git a/frontend/app/src/lib/api/generated/data-contracts.ts b/frontend/app/src/lib/api/generated/data-contracts.ts index fe22cff16..7d710279b 100644 --- a/frontend/app/src/lib/api/generated/data-contracts.ts +++ b/frontend/app/src/lib/api/generated/data-contracts.ts @@ -533,3 +533,35 @@ export interface Worker { /** The recent step runs for this worker. */ recentStepRuns?: StepRun[]; } + +export interface APIToken { + metadata: APIResourceMeta; + /** + * The name of the API token. + * @maxLength 255 + */ + name: string; + /** + * When the API token expires. + * @format date-time + */ + expiresAt: string; +} + +export interface CreateAPITokenRequest { + /** + * A name for the API token. + * @maxLength 255 + */ + name: string; +} + +export interface CreateAPITokenResponse { + /** The API token. */ + token: string; +} + +export interface ListAPITokensResponse { + pagination?: PaginationResponse; + rows?: APIToken[]; +} diff --git a/frontend/app/src/lib/api/queries.ts b/frontend/app/src/lib/api/queries.ts index 5e71952ec..5b59aae8b 100644 --- a/frontend/app/src/lib/api/queries.ts +++ b/frontend/app/src/lib/api/queries.ts @@ -26,6 +26,12 @@ export const queries = createQueryKeyStore({ queryFn: async () => (await api.tenantMemberList(tenant)).data, }), }, + tokens: { + list: (tenant: string) => ({ + queryKey: ['api-token:list', tenant], + queryFn: async () => (await api.apiTokenList(tenant)).data, + }), + }, invites: { list: (tenant: string) => ({ queryKey: ['tenant-invite:list', tenant], diff --git a/frontend/app/src/pages/main/tenant-settings/components/api-tokens-columns.tsx b/frontend/app/src/pages/main/tenant-settings/components/api-tokens-columns.tsx new file mode 100644 index 000000000..0aea55f9c --- /dev/null +++ b/frontend/app/src/pages/main/tenant-settings/components/api-tokens-columns.tsx @@ -0,0 +1,55 @@ +import { ColumnDef } from '@tanstack/react-table'; +import { DataTableColumnHeader } from '../../../../components/molecules/data-table/data-table-column-header'; +import { APIToken } from '@/lib/api'; +import { DataTableRowActions } from '@/components/molecules/data-table/data-table-row-actions'; +import { relativeDate } from '@/lib/utils'; + +export const columns = ({ + onRevokeClick, +}: { + onRevokeClick: (row: APIToken) => void; +}): ColumnDef[] => { + return [ + { + accessorKey: 'name', + header: ({ column }) => ( + + ), + cell: ({ row }) =>
{row.getValue('name')}
, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: 'created', + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
{relativeDate(row.original.metadata.createdAt)}
+ ), + }, + { + accessorKey: 'Expires', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return
{relativeDate(row.original.expiresAt)}
; + }, + }, + { + id: 'actions', + cell: ({ row }) => ( + onRevokeClick(row.original), + }, + ]} + /> + ), + }, + ]; +}; diff --git a/frontend/app/src/pages/main/tenant-settings/components/create-token-dialog.tsx b/frontend/app/src/pages/main/tenant-settings/components/create-token-dialog.tsx new file mode 100644 index 000000000..533c73964 --- /dev/null +++ b/frontend/app/src/pages/main/tenant-settings/components/create-token-dialog.tsx @@ -0,0 +1,103 @@ +import { + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Code } from '@/components/ui/code'; +import { Button } from '@/components/ui/button'; +import { z } from 'zod'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Label } from '@/components/ui/label'; +import { Input } from '@/components/ui/input'; +import { cn } from '@/lib/utils'; +import { Spinner } from '@/components/ui/loading'; + +const schema = z.object({ + name: z.string().min(1).max(255), +}); + +interface CreateTokenDialogProps { + className?: string; + token?: string; + onSubmit: (opts: z.infer) => void; + isLoading: boolean; + fieldErrors?: Record; +} + +export function CreateTokenDialog({ + className, + token, + ...props +}: CreateTokenDialogProps) { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm>({ + resolver: zodResolver(schema), + }); + + const nameError = errors.name?.message?.toString() || props.fieldErrors?.name; + + if (token) { + return ( + + + Keep it secret, keep it safe + +

+ This is the only time we will show you this token. Make sure to copy + it somewhere safe. +

+ + {token} + +
+ ); + } + + // TODO: add a name for the token + + return ( + + + Create a new API token + +
+
{ + props.onSubmit(d); + })} + > +
+
+ + + {nameError && ( +
{nameError}
+ )} +
+ +
+
+
+
+ ); +} diff --git a/frontend/app/src/pages/main/tenant-settings/components/invites-columns.tsx b/frontend/app/src/pages/main/tenant-settings/components/invites-columns.tsx index 86c2e4b77..fa6f7aed4 100644 --- a/frontend/app/src/pages/main/tenant-settings/components/invites-columns.tsx +++ b/frontend/app/src/pages/main/tenant-settings/components/invites-columns.tsx @@ -43,7 +43,6 @@ export const columns = ({ ), cell: ({ row }) => { - console.log(row.original.expires); return
{relativeDate(row.original.expires)}
; }, }, diff --git a/frontend/app/src/pages/main/tenant-settings/components/revoke-token-form.tsx b/frontend/app/src/pages/main/tenant-settings/components/revoke-token-form.tsx new file mode 100644 index 000000000..ced7e48ed --- /dev/null +++ b/frontend/app/src/pages/main/tenant-settings/components/revoke-token-form.tsx @@ -0,0 +1,50 @@ +import { Button } from '@/components/ui/button'; +import { Spinner } from '@/components/ui/loading.tsx'; +import { + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { APIToken } from '@/lib/api'; + +interface RevokeTokenFormProps { + className?: string; + onSubmit: (apiToken: APIToken) => void; + onCancel: () => void; + apiToken: APIToken; + isLoading: boolean; +} + +export function RevokeTokenForm({ className, ...props }: RevokeTokenFormProps) { + return ( + + + Delete invite + +
+
+ Are you sure you want to revoke the API token {props.apiToken.name}? +
+
+ + +
+
+
+ ); +} diff --git a/frontend/app/src/pages/main/tenant-settings/index.tsx b/frontend/app/src/pages/main/tenant-settings/index.tsx index 6ee027416..e24108a3f 100644 --- a/frontend/app/src/pages/main/tenant-settings/index.tsx +++ b/frontend/app/src/pages/main/tenant-settings/index.tsx @@ -7,6 +7,8 @@ import { CreateInviteForm } from './components/create-invite-form'; import { useApiError } from '@/lib/hooks'; import { useMutation, useQuery } from '@tanstack/react-query'; import api, { + APIToken, + CreateAPITokenRequest, CreateTenantInviteRequest, TenantInvite, UpdateTenantInviteRequest, @@ -16,8 +18,11 @@ import { Dialog } from '@/components/ui/dialog'; import { DataTable } from '@/components/molecules/data-table/data-table'; import { columns } from './components/invites-columns'; import { columns as membersColumns } from './components/members-columns'; +import { columns as apiTokensColumns } from './components/api-tokens-columns'; import { UpdateInviteForm } from './components/update-invite-form'; import { DeleteInviteForm } from './components/delete-invite-form'; +import { CreateTokenDialog } from './components/create-token-dialog'; +import { RevokeTokenForm } from './components/revoke-token-form'; export default function TenantSettings() { const { tenant } = useOutletContext(); @@ -32,6 +37,8 @@ export default function TenantSettings() { + +
); @@ -247,3 +254,137 @@ function DeleteInvite({ ); } + +function TokensList() { + const { tenant } = useOutletContext(); + const [showTokenDialog, setShowTokenDialog] = useState(false); + const [revokeToken, setRevokeToken] = useState(null); + + const listTokensQuery = useQuery({ + ...queries.tokens.list(tenant.metadata.id), + }); + + const cols = apiTokensColumns({ + onRevokeClick: (row) => { + setRevokeToken(row); + }, + }); + + return ( +
+
+

+ API Tokens +

+ +
+ + row.metadata.id} + /> + {showTokenDialog && ( + { + listTokensQuery.refetch(); + }} + /> + )} + {revokeToken && ( + setRevokeToken(null)} + onSuccess={() => { + setRevokeToken(null); + listTokensQuery.refetch(); + }} + /> + )} +
+ ); +} + +function CreateToken({ + tenant, + showTokenDialog, + setShowTokenDialog, + onSuccess, +}: { + tenant: string; + onSuccess: () => void; + showTokenDialog: boolean; + setShowTokenDialog: (show: boolean) => void; +}) { + const [generatedToken, setGeneratedToken] = useState(); + const [fieldErrors, setFieldErrors] = useState>({}); + const { handleApiError } = useApiError({ + setFieldErrors: setFieldErrors, + }); + + const createTokenMutation = useMutation({ + mutationKey: ['api-token:create', tenant], + mutationFn: async (data: CreateAPITokenRequest) => { + const res = await api.apiTokenCreate(tenant, data); + return res.data; + }, + onSuccess: (data) => { + setGeneratedToken(data.token); + onSuccess(); + }, + onError: handleApiError, + }); + + return ( + + + + ); +} + +function RevokeToken({ + tenant, + apiToken, + setShowTokenRevoke, + onSuccess, +}: { + tenant: string; + apiToken: APIToken; + setShowTokenRevoke: (show: boolean) => void; + onSuccess: () => void; +}) { + const { handleApiError } = useApiError({}); + + const revokeMutation = useMutation({ + mutationKey: ['api-token:revoke', tenant, apiToken], + mutationFn: async () => { + await api.apiTokenUpdateRevoke(apiToken.metadata.id); + }, + onSuccess: onSuccess, + onError: handleApiError, + }); + + return ( + + revokeMutation.mutate()} + onCancel={() => setShowTokenRevoke(false)} + /> + + ); +} diff --git a/go.mod b/go.mod index ae7d4cb30..99ab55ebe 100644 --- a/go.mod +++ b/go.mod @@ -20,11 +20,14 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.16.0 github.com/steebchen/prisma-client-go v0.32.1 + github.com/tink-crypto/tink-go v0.0.0-20230613075026-d6de17e3f164 + github.com/tink-crypto/tink-go-gcpkms v0.0.0-20230602082706-31d0d09ccc8d go.opentelemetry.io/otel v1.21.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 go.opentelemetry.io/otel/sdk v1.21.0 go.opentelemetry.io/otel/trace v1.21.0 + google.golang.org/api v0.157.0 sigs.k8s.io/yaml v1.4.0 ) @@ -62,8 +65,6 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/tink-crypto/tink-go v0.0.0-20230613075026-d6de17e3f164 // indirect - github.com/tink-crypto/tink-go-gcpkms v0.0.0-20230602082706-31d0d09ccc8d // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opencensus.io v0.24.0 // indirect @@ -72,7 +73,6 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.157.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect ) @@ -87,6 +87,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.5.0 github.com/gorilla/schema v1.2.1 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1 github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -105,8 +106,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.8.4 github.com/subosito/gotenv v1.6.0 // indirect - github.com/tink-crypto/tink-go-gcpkms/v2 v2.1.0 - github.com/tink-crypto/tink-go/v2 v2.1.0 golang.org/x/crypto v0.18.0 golang.org/x/net v0.20.0 // indirect golang.org/x/oauth2 v0.16.0 diff --git a/go.sum b/go.sum index 40efdb607..c4cdf1072 100644 --- a/go.sum +++ b/go.sum @@ -207,6 +207,8 @@ github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8L github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1 h1:HcUWd006luQPljE73d5sk+/VgYPGUReEVz2y1/qylwY= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -317,8 +319,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= -github.com/steebchen/prisma-client-go v0.31.4-0.20231228102837-d2b2373128a2 h1:8vofR6qaIWoTi2AGymk1MlipkUWLjBNjKjYA8DYaHps= -github.com/steebchen/prisma-client-go v0.31.4-0.20231228102837-d2b2373128a2/go.mod h1:ksKELgUZSn56rbAv1jlF8D7o8V6lis0Tc2LEgv2qNbs= github.com/steebchen/prisma-client-go v0.32.1 h1:7jUke4XVSzUkV2TN4VhYSwjU7qizM9w475gpudkLCNI= github.com/steebchen/prisma-client-go v0.32.1/go.mod h1:shY2GTQyv15WYTE4p2zffr01ratTzX0zXtBWnDHiLpo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -341,10 +341,6 @@ github.com/tink-crypto/tink-go v0.0.0-20230613075026-d6de17e3f164 h1:yhVO0Yhq84F github.com/tink-crypto/tink-go v0.0.0-20230613075026-d6de17e3f164/go.mod h1:HhtDVdE/PRZFRia834tkmcwuscnaAzda1RJUW9Pr3Rg= github.com/tink-crypto/tink-go-gcpkms v0.0.0-20230602082706-31d0d09ccc8d h1:+In5BwTMe2nF3FC6LrYqg71jDyaOOMZ4EQBFUhFq23g= github.com/tink-crypto/tink-go-gcpkms v0.0.0-20230602082706-31d0d09ccc8d/go.mod h1:TXKMH7TDt0h7QXtI9TdYPyly6xZL+ooPpbw30qekmEc= -github.com/tink-crypto/tink-go-gcpkms/v2 v2.1.0 h1:A/2tIdYXqUuVZeWy0Yq/PWKsXgebzMyh5mLbpNEMVUo= -github.com/tink-crypto/tink-go-gcpkms/v2 v2.1.0/go.mod h1:QXPc/i5yUEWWZ4lbe2WOam1kDdrXjGHRjl0Lzo7IQDU= -github.com/tink-crypto/tink-go/v2 v2.1.0 h1:QXFBguwMwTIaU17EgZpEJWsUSc60b1BAGTzBIoMdmok= -github.com/tink-crypto/tink-go/v2 v2.1.0/go.mod h1:y1TnYFt1i2eZVfx4OGc+C+EMp4CoKWAw2VSEuoicHHI= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -392,8 +388,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -466,8 +460,6 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -492,8 +484,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -540,8 +530,6 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -560,8 +548,7 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -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.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.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= @@ -642,7 +629,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= @@ -682,14 +668,10 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -708,8 +690,6 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -724,8 +704,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/auth/token/token.go b/internal/auth/token/token.go index b39c06965..15836d86c 100644 --- a/internal/auth/token/token.go +++ b/internal/auth/token/token.go @@ -12,8 +12,8 @@ import ( ) type JWTManager interface { - GenerateTenantToken(tenantId string) (string, error) - ValidateTenantToken(token, tenantId string) error + GenerateTenantToken(tenantId, name string) (string, error) + ValidateTenantToken(token string) (string, error) } type TokenOpts struct { @@ -35,7 +35,7 @@ func NewJWTManager(encryption encryption.EncryptionService, tokenRepo repository } } -func (j *jwtManagerImpl) GenerateTenantToken(tenantId string) (string, error) { +func (j *jwtManagerImpl) GenerateTenantToken(tenantId, name string) (string, error) { // Retrieve the JWT Signer primitive from privateKeysetHandle. signer, err := jwt.NewSigner(j.encryption.GetPrivateJWTHandle()) @@ -62,6 +62,7 @@ func (j *jwtManagerImpl) GenerateTenantToken(tenantId string) (string, error) { ID: tokenId, ExpiresAt: expiresAt, TenantId: &tenantId, + Name: &name, }) if err != nil { @@ -71,13 +72,13 @@ func (j *jwtManagerImpl) GenerateTenantToken(tenantId string) (string, error) { return token, nil } -func (j *jwtManagerImpl) ValidateTenantToken(token, tenantId string) error { +func (j *jwtManagerImpl) ValidateTenantToken(token string) (tenantId string, err error) { // Retrieve the Verifier primitive from publicKeysetHandle. // TODO: move verifier to the constructor verifier, err := jwt.NewVerifier(j.encryption.GetPublicJWTHandle()) if err != nil { - return fmt.Errorf("failed to create JWT Verifier: %v", err) + return "", fmt.Errorf("failed to create JWT Verifier: %v", err) } // Verify the signed token. For this example, we use a fixed date. Usually, you would @@ -92,61 +93,57 @@ func (j *jwtManagerImpl) ValidateTenantToken(token, tenantId string) error { }) if err != nil { - return fmt.Errorf("failed to create JWT Validator: %v", err) + return "", fmt.Errorf("failed to create JWT Validator: %v", err) } verifiedJwt, err := verifier.VerifyAndDecode(token, validator) if err != nil { - return fmt.Errorf("failed to verify and decode JWT: %v", err) + return "", fmt.Errorf("failed to verify and decode JWT: %v", err) } // Read the token from the database and make sure it's not revoked if hasTokenId := verifiedJwt.HasStringClaim("token_id"); !hasTokenId { - return fmt.Errorf("token does not have token_id claim") + return "", fmt.Errorf("token does not have token_id claim") } tokenId, err := verifiedJwt.StringClaim("token_id") if err != nil { - return fmt.Errorf("failed to read token_id claim: %v", err) + return "", fmt.Errorf("failed to read token_id claim: %v", err) } // read the token from the database dbToken, err := j.tokenRepo.GetAPITokenById(tokenId) if err != nil { - return fmt.Errorf("failed to read token from database: %v", err) + return "", fmt.Errorf("failed to read token from database: %v", err) } if dbToken.Revoked { - return fmt.Errorf("token has been revoked") + return "", fmt.Errorf("token has been revoked") } if expiresAt, ok := dbToken.ExpiresAt(); ok && expiresAt.Before(time.Now()) { - return fmt.Errorf("token has expired") + return "", fmt.Errorf("token has expired") } // ensure the subject of the token matches the tenantId if hasSubject := verifiedJwt.HasSubject(); !hasSubject { - return fmt.Errorf("token does not have subject claim") + return "", fmt.Errorf("token does not have subject claim") } subject, err := verifiedJwt.Subject() if err != nil { - return fmt.Errorf("failed to read subject claim: %v", err) + return "", fmt.Errorf("failed to read subject claim: %v", err) } - if subject != tenantId { - return fmt.Errorf("token subject does not match tenantId") - } - - return nil + return subject, nil } func (j *jwtManagerImpl) getJWTOptionsForTenant(tenantId string) (tokenId string, expiresAt time.Time, opts *jwt.RawJWTOptions) { - expiresAt = time.Now().Add(30 * 24 * time.Hour) + expiresAt = time.Now().Add(90 * 24 * time.Hour) iAt := time.Now() audience := j.opts.Audience subject := tenantId diff --git a/internal/auth/token/token_test.go b/internal/auth/token/token_test.go index cd5363e04..2951b10cd 100644 --- a/internal/auth/token/token_test.go +++ b/internal/auth/token/token_test.go @@ -22,16 +22,34 @@ func TestCreateTenantToken(t *testing.T) { tenantId := uuid.New().String() - token, err := jwtManager.GenerateTenantToken(tenantId) + // create the tenant + slugSuffix, err := encryption.GenerateRandomBytes(8) + + if err != nil { + t.Fatal(err.Error()) + } + + _, err = conf.Repository.Tenant().CreateTenant(&repository.CreateTenantOpts{ + ID: &tenantId, + Name: "test-tenant", + Slug: fmt.Sprintf("test-tenant-%s", slugSuffix), + }) + + if err != nil { + t.Fatal(err.Error()) + } + + token, err := jwtManager.GenerateTenantToken(tenantId, "test token") if err != nil { t.Fatal(err.Error()) } // validate the token - err = jwtManager.ValidateTenantToken(token, tenantId) + newTenantId, err := jwtManager.ValidateTenantToken(token) assert.NoError(t, err) + assert.Equal(t, tenantId, newTenantId) return nil }) @@ -60,14 +78,14 @@ func TestRevokeTenantToken(t *testing.T) { t.Fatal(err.Error()) } - token, err := jwtManager.GenerateTenantToken(tenantId) + token, err := jwtManager.GenerateTenantToken(tenantId, "test token") if err != nil { t.Fatal(err.Error()) } // validate the token - err = jwtManager.ValidateTenantToken(token, tenantId) + _, err = jwtManager.ValidateTenantToken(token) assert.NoError(t, err) @@ -86,7 +104,7 @@ func TestRevokeTenantToken(t *testing.T) { } // validate the token again - err = jwtManager.ValidateTenantToken(token, tenantId) + _, err = jwtManager.ValidateTenantToken(token) assert.Error(t, err) diff --git a/internal/config/client/client.go b/internal/config/client/client.go index c81a29875..41772cfe9 100644 --- a/internal/config/client/client.go +++ b/internal/config/client/client.go @@ -11,6 +11,10 @@ import ( type ClientConfigFile struct { TenantId string `mapstructure:"tenantId" json:"tenantId,omitempty"` + Token string `mapstructure:"token" json:"token,omitempty"` + + HostPort string `mapstructure:"hostPort" json:"hostPort,omitempty"` + TLS ClientTLSConfigFile `mapstructure:"tls" json:"tls,omitempty"` } @@ -22,14 +26,18 @@ type ClientTLSConfigFile struct { type ClientConfig struct { TenantId string + Token string TLSConfig *tls.Config } func BindAllEnv(v *viper.Viper) { _ = v.BindEnv("tenantId", "HATCHET_CLIENT_TENANT_ID") + _ = v.BindEnv("token", "HATCHET_CLIENT_TOKEN") + _ = v.BindEnv("hostPort", "HATCHET_CLIENT_HOST_PORT") // tls options + _ = v.BindEnv("tls.base.tlsStrategy", "HATCHET_CLIENT_TLS_STRATEGY") _ = v.BindEnv("tls.base.tlsCertFile", "HATCHET_CLIENT_TLS_CERT_FILE") _ = v.BindEnv("tls.base.tlsKeyFile", "HATCHET_CLIENT_TLS_KEY_FILE") _ = v.BindEnv("tls.base.tlsRootCAFile", "HATCHET_CLIENT_TLS_ROOT_CA_FILE") diff --git a/internal/config/loader/loader.go b/internal/config/loader/loader.go index 866fde240..44867bbd9 100644 --- a/internal/config/loader/loader.go +++ b/internal/config/loader/loader.go @@ -202,11 +202,16 @@ func GetServerConfigFromConfigfile(dc *database.Config, cf *server.ServerConfigF } if cf.Encryption.CloudKMS.Enabled { - // encryptionSvc, err = encryption.NewCloudKMSEncryption(cf.Encryption.CloudKMS.KeyURI, []byte(cf.Encryption.CloudKMS.CredentialsJSON)) + encryptionSvc, err = encryption.NewCloudKMSEncryption( + cf.Encryption.CloudKMS.KeyURI, + []byte(cf.Encryption.CloudKMS.CredentialsJSON), + []byte(cf.Encryption.JWT.PrivateJWTKeyset), + []byte(cf.Encryption.JWT.PublicJWTKeyset), + ) - // if err != nil { - // return nil, fmt.Errorf("could not create CloudKMS encryption service: %w", err) - // } + if err != nil { + return nil, fmt.Errorf("could not create CloudKMS encryption service: %w", err) + } } auth := server.AuthConfig{ diff --git a/internal/config/loader/loaderutils/tls.go b/internal/config/loader/loaderutils/tls.go index 67b62550b..7004cff8b 100644 --- a/internal/config/loader/loaderutils/tls.go +++ b/internal/config/loader/loaderutils/tls.go @@ -10,15 +10,26 @@ import ( "github.com/hatchet-dev/hatchet/internal/config/shared" ) -func LoadClientTLSConfig(tlsConfig *client.ClientTLSConfigFile) (*tls.Config, error) { +func LoadClientTLSConfig(tlsConfig *client.ClientTLSConfigFile, serverName string) (*tls.Config, error) { res, ca, err := LoadBaseTLSConfig(&tlsConfig.Base) if err != nil { return nil, err } - res.ServerName = tlsConfig.TLSServerName - res.RootCAs = ca + res.ServerName = serverName + + switch tlsConfig.Base.TLSStrategy { + case "tls": + if ca != nil { + res.RootCAs = ca + } + case "mtls": + res.ServerName = tlsConfig.TLSServerName + res.RootCAs = ca + default: + return nil, fmt.Errorf("invalid TLS strategy: %s", tlsConfig.Base.TLSStrategy) + } return res, nil } @@ -30,8 +41,19 @@ func LoadServerTLSConfig(tlsConfig *shared.TLSConfigFile) (*tls.Config, error) { return nil, err } - res.ClientAuth = tls.RequireAndVerifyClientCert - res.ClientCAs = ca + switch tlsConfig.TLSStrategy { + case "tls": + res.ClientAuth = tls.VerifyClientCertIfGiven + case "mtls": + if ca == nil { + return nil, fmt.Errorf("Client CA is required for mTLS") + } + + res.ClientAuth = tls.RequireAndVerifyClientCert + res.ClientCAs = ca + default: + return nil, fmt.Errorf("invalid TLS strategy: %s", tlsConfig.TLSStrategy) + } return res, nil } @@ -45,8 +67,6 @@ func LoadBaseTLSConfig(tlsConfig *shared.TLSConfigFile) (*tls.Config, *x509.Cert x509Cert, err = tls.X509KeyPair([]byte(tlsConfig.TLSCert), []byte(tlsConfig.TLSKey)) case tlsConfig.TLSCertFile != "" && tlsConfig.TLSKeyFile != "": x509Cert, err = tls.LoadX509KeyPair(tlsConfig.TLSCertFile, tlsConfig.TLSKeyFile) - default: - return nil, nil, fmt.Errorf("no cert or key provided") } var caBytes []byte @@ -56,18 +76,25 @@ func LoadBaseTLSConfig(tlsConfig *shared.TLSConfigFile) (*tls.Config, *x509.Cert caBytes = []byte(tlsConfig.TLSRootCA) case tlsConfig.TLSRootCAFile != "": caBytes, err = os.ReadFile(tlsConfig.TLSRootCAFile) - default: - return nil, nil, fmt.Errorf("no root CA provided") } - ca := x509.NewCertPool() + var ca *x509.CertPool + + if len(caBytes) != 0 { + ca = x509.NewCertPool() + + if ok := ca.AppendCertsFromPEM(caBytes); !ok { + return nil, nil, fmt.Errorf("could not append root CA to cert pool: %w", err) + } + } + + res := &tls.Config{ + MinVersion: tls.VersionTLS13, + } - if ok := ca.AppendCertsFromPEM(caBytes); !ok { - return nil, nil, fmt.Errorf("could not append root CA to cert pool: %w", err) + if len(x509Cert.Certificate) != 0 { + res.Certificates = []tls.Certificate{x509Cert} } - return &tls.Config{ - Certificates: []tls.Certificate{x509Cert}, - MinVersion: tls.VersionTLS13, - }, ca, nil + return res, ca, nil } diff --git a/internal/config/server/server.go b/internal/config/server/server.go index ad0645d41..57b367403 100644 --- a/internal/config/server/server.go +++ b/internal/config/server/server.go @@ -184,6 +184,14 @@ func BindAllEnv(v *viper.Viper) { _ = v.BindEnv("runtime.grpcInsecure", "SERVER_GRPC_INSECURE") _ = v.BindEnv("services", "SERVER_SERVICES") + // encryption options + _ = v.BindEnv("encryption.masterKeyset", "SERVER_ENCRYPTION_MASTER_KEYSET") + _ = v.BindEnv("encryption.jwt.publicJWTKeyset", "SERVER_ENCRYPTION_JWT_PUBLIC_KEYSET") + _ = v.BindEnv("encryption.jwt.privateJWTKeyset", "SERVER_ENCRYPTION_JWT_PRIVATE_KEYSET") + _ = v.BindEnv("encryption.cloudKms.enabled", "SERVER_ENCRYPTION_CLOUDKMS_ENABLED") + _ = v.BindEnv("encryption.cloudKms.keyURI", "SERVER_ENCRYPTION_CLOUDKMS_KEY_URI") + _ = v.BindEnv("encryption.cloudKms.credentialsJSON", "SERVER_ENCRYPTION_CLOUDKMS_CREDENTIALS_JSON") + // auth options _ = v.BindEnv("auth.restrictedEmailDomains", "SERVER_AUTH_RESTRICTED_EMAIL_DOMAINS") _ = v.BindEnv("auth.basicAuthEnabled", "SERVER_AUTH_BASIC_AUTH_ENABLED") @@ -202,6 +210,7 @@ func BindAllEnv(v *viper.Viper) { _ = v.BindEnv("taskQueue.rabbitmq.url", "SERVER_TASKQUEUE_RABBITMQ_URL") // tls options + _ = v.BindEnv("tls.tlsStrategy", "SERVER_TLS_STRATEGY") _ = v.BindEnv("tls.tlsCert", "SERVER_TLS_CERT") _ = v.BindEnv("tls.tlsCertFile", "SERVER_TLS_CERT_FILE") _ = v.BindEnv("tls.tlsKey", "SERVER_TLS_KEY") diff --git a/internal/config/shared/shared.go b/internal/config/shared/shared.go index afdae0def..2271d68ce 100644 --- a/internal/config/shared/shared.go +++ b/internal/config/shared/shared.go @@ -1,6 +1,9 @@ package shared type TLSConfigFile struct { + // TLSStrategy can be "tls" or "mtls" + TLSStrategy string `mapstructure:"tlsStrategy" json:"tlsStrategy,omitempty" default:"tls"` + TLSCert string `mapstructure:"tlsCert" json:"tlsCert,omitempty"` TLSCertFile string `mapstructure:"tlsCertFile" json:"tlsCertFile,omitempty"` TLSKey string `mapstructure:"tlsKey" json:"tlsKey,omitempty"` diff --git a/internal/encryption/cloudkms.go b/internal/encryption/cloudkms.go index c5b985c4b..725b04e56 100644 --- a/internal/encryption/cloudkms.go +++ b/internal/encryption/cloudkms.go @@ -7,7 +7,6 @@ import ( "github.com/tink-crypto/tink-go-gcpkms/integration/gcpkms" "github.com/tink-crypto/tink-go/aead" "github.com/tink-crypto/tink-go/core/registry" - "github.com/tink-crypto/tink-go/jwt" "github.com/tink-crypto/tink-go/keyset" "google.golang.org/api/option" ) @@ -19,17 +18,39 @@ type cloudkmsEncryptionService struct { } // NewCloudKMSEncryption creates a GCP CloudKMS-backed encryption service. -func NewCloudKMSEncryption(keyUri string, credentialsJSON []byte) (*cloudkmsEncryptionService, error) { +func NewCloudKMSEncryption(keyUri string, credentialsJSON, privateEc256, publicEc256 []byte) (*cloudkmsEncryptionService, error) { client, err := gcpkms.NewClientWithOptions(context.Background(), keyUri, option.WithCredentialsJSON(credentialsJSON)) if err != nil { return nil, err } - return newWithClient(client, keyUri) + return newWithClient(client, keyUri, privateEc256, publicEc256) } -func newWithClient(client registry.KMSClient, keyUri string) (*cloudkmsEncryptionService, error) { +func GenerateJWTKeysetsFromCloudKMS(keyUri string, credentialsJSON []byte) (privateEc256 []byte, publicEc256 []byte, err error) { + client, err := gcpkms.NewClientWithOptions(context.Background(), keyUri, option.WithCredentialsJSON(credentialsJSON)) + + if err != nil { + return nil, nil, err + } + + return generateJWTKeysetsWithClient(keyUri, client) +} + +func generateJWTKeysetsWithClient(keyUri string, client registry.KMSClient) (privateEc256 []byte, publicEc256 []byte, err error) { + registry.RegisterKMSClient(client) + + remote, err := client.GetAEAD(keyUri) + + if err != nil { + return nil, nil, err + } + + return generateJWTKeysets(remote) +} + +func newWithClient(client registry.KMSClient, keyUri string, privateEc256, publicEc256 []byte) (*cloudkmsEncryptionService, error) { registry.RegisterKMSClient(client) dek := aead.AES128CTRHMACSHA256KeyTemplate() @@ -52,23 +73,22 @@ func newWithClient(client registry.KMSClient, keyUri string) (*cloudkmsEncryptio return nil, fmt.Errorf("failed to create envelope") } - jwtTemplate := jwt.ES256Template() - - jwtHandle, err := keyset.NewHandle(jwtTemplate) + privateEc256Handle, err := handleFromBytes(privateEc256, remote) if err != nil { return nil, err } - _, err = jwt.JWKSetFromPublicKeysetHandle(jwtHandle) + publicEc256Handle, err := handleFromBytes(publicEc256, remote) if err != nil { return nil, err } return &cloudkmsEncryptionService{ - key: envelope, - // jwtHandle: jwtHandle, + key: envelope, + privateEc256Handle: privateEc256Handle, + publicEc256Handle: publicEc256Handle, }, nil } @@ -80,6 +100,10 @@ func (svc *cloudkmsEncryptionService) Decrypt(ciphertext []byte, dataId string) return decrypt(svc.key, ciphertext, dataId) } -// func (svc *cloudkmsEncryptionService) GetJWTHandle() *keyset.Handle { -// return svc.jwtHandle -// } +func (svc *cloudkmsEncryptionService) GetPrivateJWTHandle() *keyset.Handle { + return svc.privateEc256Handle +} + +func (svc *cloudkmsEncryptionService) GetPublicJWTHandle() *keyset.Handle { + return svc.publicEc256Handle +} diff --git a/internal/encryption/cloudkms_test.go b/internal/encryption/cloudkms_test.go index 78fd5ef67..cd81b8f46 100644 --- a/internal/encryption/cloudkms_test.go +++ b/internal/encryption/cloudkms_test.go @@ -17,21 +17,28 @@ func TestNewCloudKMSEncryptionValid(t *testing.T) { client, err := fakekms.NewClient(fakeKeyURI) assert.NoError(t, err) + // generate JWT keysets + privateEc256, publicEc256, err := generateJWTKeysetsWithClient(fakeKeyURI, client) + + if err != nil { + t.Fatal(err) + } + // Create encryption service with valid key URI and credentials - svc, err := newWithClient(client, fakeKeyURI) + svc, err := newWithClient(client, fakeKeyURI, privateEc256, publicEc256) assert.NoError(t, err) assert.NotNil(t, svc) } func TestNewCloudKMSEncryptionInvalidKeyUri(t *testing.T) { // Create encryption service with invalid key URI - _, err := NewCloudKMSEncryption("invalid-key-uri", fakeCredentialsJSON) + _, err := NewCloudKMSEncryption("invalid-key-uri", fakeCredentialsJSON, nil, nil) assert.Error(t, err) } func TestNewCloudKMSEncryptionInvalidCredentials(t *testing.T) { // Create encryption service with invalid credentials - _, err := NewCloudKMSEncryption(fakeKeyURI, []byte("invalid credentials")) + _, err := NewCloudKMSEncryption(fakeKeyURI, []byte("invalid credentials"), nil, nil) assert.Error(t, err) } @@ -40,8 +47,15 @@ func TestEncryptDecryptCloudKMS(t *testing.T) { client, err := fakekms.NewClient(fakeKeyURI) assert.NoError(t, err) + // generate JWT keysets + privateEc256, publicEc256, err := generateJWTKeysetsWithClient(fakeKeyURI, client) + + if err != nil { + t.Fatal(err) + } + // Create encryption service with valid key URI and credentials - svc, err := newWithClient(client, fakeKeyURI) + svc, err := newWithClient(client, fakeKeyURI, privateEc256, publicEc256) if err != nil { t.Fatal(err) @@ -69,8 +83,15 @@ func TestEncryptDecryptCloudKMSWithEmptyDataID(t *testing.T) { client, err := fakekms.NewClient(fakeKeyURI) assert.NoError(t, err) + // generate JWT keysets + privateEc256, publicEc256, err := generateJWTKeysetsWithClient(fakeKeyURI, client) + + if err != nil { + t.Fatal(err) + } + // Create encryption service with valid key URI and credentials - svc, err := newWithClient(client, fakeKeyURI) + svc, err := newWithClient(client, fakeKeyURI, privateEc256, publicEc256) if err != nil { t.Fatal(err) diff --git a/internal/encryption/local.go b/internal/encryption/local.go index 9bf232231..9268309aa 100644 --- a/internal/encryption/local.go +++ b/internal/encryption/local.go @@ -46,51 +46,6 @@ func NewLocalEncryption(masterKey []byte, privateEc256 []byte, publicEc256 []byt return nil, err } - // // base64-decode bytes - // keysetJsonBytes := make([]byte, base64.RawStdEncoding.DecodedLen(len(keysetBytes))) - // _, err := base64.RawStdEncoding.Decode(keysetJsonBytes, keysetBytes) - - // if err != nil { - // return nil, fmt.Errorf("failed to decode keyset bytes: %w", err) - // } - - // // read keyset - // handle, err := insecurecleartextkeyset.Read(keyset.NewJSONReader(bytes.NewReader(keysetJsonBytes))) - - // if err != nil { - // return nil, fmt.Errorf("failed to read keyset: %w", err) - // } - - // jwtTemplate := jwt.ES256Template() - // jwtHandle, err := keyset.NewHandle(jwtTemplate) - - // if err != nil { - // return nil, err - // } - - // kh, err := keyset.NewHandle(signature.ECDSAP256KeyTemplate()) // Other key templates can also be used. - // if err != nil { - // return nil, fmt.Errorf("failed to create new handle: %w", err) - // } - - // publicKeyset, err := kh.Public() - - // if err != nil { - // return nil, fmt.Errorf("failed to get public keyset: %w", err) - // } - - // _, err = jwt.JWKSetFromPublicKeysetHandle(publicKeyset) - - // if err != nil { - // return nil, fmt.Errorf("failed to create JWK set: %w", err) - // } - - // a, err := aead.New(handle) - - // if err != nil { - // return nil, fmt.Errorf("failed to create AEAD: %w", err) - // } - envelope := aead.NewKMSEnvelopeAEAD2(aead.AES128GCMKeyTemplate(), a) if envelope == nil { @@ -176,47 +131,6 @@ func generateJWTKeysets(masterKey tink.AEAD) (privateEc256 []byte, publicEc256 [ return } -// // NewLocalKeyset generates a new local keyset and returns it as a base64-encoded JSON string. -// // Note that this uses the insecurecleartextkeyset package. It's recommended to use the CloudKMS -// // service, but this will work for smaller deployments. If the keyset is exposed, all API tokens -// // must be revoked and a new local keyset should be created. -// func NewLocalKeysets() (aes256Gcm []byte, privateEc256 []byte, publicEc256 []byte, err error) { -// // Generate a new keyset handle for the primitive we want to use. -// aeadTemplate := aead.AES256GCMKeyTemplate() - -// aes256GcmHandle, err := keyset.NewHandle(aeadTemplate) - -// if err != nil { -// return -// } - -// if aes256Gcm, err = bytesFromHandle(aes256GcmHandle); err != nil { -// return -// } - -// kh, err := keyset.NewHandle(signature.ECDSAP256KeyTemplate()) - -// if err != nil { -// return -// } - -// if privateEc256, err = bytesFromHandle(kh); err != nil { -// return -// } - -// publicKeyset, err := kh.Public() - -// if err != nil { -// return -// } - -// if publicEc256, err = bytesFromHandle(publicKeyset); err != nil { -// return -// } - -// return -// } - // bytesFromHandle returns the encrypted keyset in base64-encoded JSON format, encrypted with the // masterKey func bytesFromHandle(kh *keyset.Handle, masterKey tink.AEAD) ([]byte, error) { diff --git a/internal/repository/api_token.go b/internal/repository/api_token.go index fe907b71b..a32df3a8a 100644 --- a/internal/repository/api_token.go +++ b/internal/repository/api_token.go @@ -15,6 +15,9 @@ type CreateAPITokenOpts struct { // (optional) A tenant ID for this API token TenantId *string `validate:"omitempty,uuid"` + + // (optional) A name for this API token + Name *string `validate:"omitempty,max=255"` } type APITokenRepository interface { diff --git a/internal/repository/prisma/api_token.go b/internal/repository/prisma/api_token.go index d8505dbef..efade3f62 100644 --- a/internal/repository/prisma/api_token.go +++ b/internal/repository/prisma/api_token.go @@ -43,6 +43,10 @@ func (a *apiTokenRepository) CreateAPIToken(opts *repository.CreateAPITokenOpts) )) } + if opts.Name != nil { + optionals = append(optionals, db.APIToken.Name.Set(*opts.Name)) + } + return a.client.APIToken.CreateOne( optionals..., ).Exec(context.Background()) @@ -62,5 +66,6 @@ func (a *apiTokenRepository) RevokeAPIToken(id string) error { func (a *apiTokenRepository) ListAPITokensByTenant(tenantId string) ([]db.APITokenModel, error) { return a.client.APIToken.FindMany( db.APIToken.TenantID.Equals(tenantId), + db.APIToken.Revoked.Equals(false), ).Exec(context.Background()) } diff --git a/internal/repository/prisma/dbsqlc/models.go b/internal/repository/prisma/dbsqlc/models.go index e87acf3bd..98f6bc620 100644 --- a/internal/repository/prisma/dbsqlc/models.go +++ b/internal/repository/prisma/dbsqlc/models.go @@ -275,14 +275,25 @@ func (ns NullWorkflowRunStatus) Value() (driver.Value, error) { return string(ns.WorkflowRunStatus), nil } +type APIToken struct { + ID pgtype.UUID `json:"id"` + CreatedAt pgtype.Timestamp `json:"createdAt"` + UpdatedAt pgtype.Timestamp `json:"updatedAt"` + ExpiresAt pgtype.Timestamp `json:"expiresAt"` + Revoked bool `json:"revoked"` + Name pgtype.Text `json:"name"` + TenantId pgtype.UUID `json:"tenantId"` +} + type Action struct { - ID string `json:"id"` + ID pgtype.UUID `json:"id"` + ActionId string `json:"actionId"` Description pgtype.Text `json:"description"` TenantId pgtype.UUID `json:"tenantId"` } type ActionToWorker struct { - A string `json:"A"` + A pgtype.UUID `json:"A"` B pgtype.UUID `json:"B"` } @@ -464,8 +475,8 @@ type UserOAuth struct { UserId pgtype.UUID `json:"userId"` Provider string `json:"provider"` ProviderUserId string `json:"providerUserId"` - AccessToken string `json:"accessToken"` - RefreshToken pgtype.Text `json:"refreshToken"` + AccessToken []byte `json:"accessToken"` + RefreshToken []byte `json:"refreshToken"` ExpiresAt pgtype.Timestamp `json:"expiresAt"` } diff --git a/internal/repository/prisma/dbsqlc/schema.sql b/internal/repository/prisma/dbsqlc/schema.sql index f88864e84..3b38ac49d 100644 --- a/internal/repository/prisma/dbsqlc/schema.sql +++ b/internal/repository/prisma/dbsqlc/schema.sql @@ -16,9 +16,23 @@ CREATE TYPE "WorkerStatus" AS ENUM ('ACTIVE', 'INACTIVE'); -- CreateEnum CREATE TYPE "WorkflowRunStatus" AS ENUM ('PENDING', 'RUNNING', 'SUCCEEDED', 'FAILED'); +-- CreateTable +CREATE TABLE "APIToken" ( + "id" UUID NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expiresAt" TIMESTAMP(3), + "revoked" BOOLEAN NOT NULL DEFAULT false, + "name" TEXT, + "tenantId" UUID, + + CONSTRAINT "APIToken_pkey" PRIMARY KEY ("id") +); + -- CreateTable CREATE TABLE "Action" ( - "id" TEXT NOT NULL, + "id" UUID NOT NULL, + "actionId" TEXT NOT NULL, "description" TEXT, "tenantId" UUID NOT NULL, @@ -228,8 +242,8 @@ CREATE TABLE "UserOAuth" ( "userId" UUID NOT NULL, "provider" TEXT NOT NULL, "providerUserId" TEXT NOT NULL, - "accessToken" TEXT NOT NULL, - "refreshToken" TEXT, + "accessToken" BYTEA NOT NULL, + "refreshToken" BYTEA, "expiresAt" TIMESTAMP(3), CONSTRAINT "UserOAuth_pkey" PRIMARY KEY ("id") @@ -376,7 +390,7 @@ CREATE TABLE "WorkflowVersion" ( -- CreateTable CREATE TABLE "_ActionToWorker" ( - "A" TEXT NOT NULL, + "A" UUID NOT NULL, "B" UUID NOT NULL ); @@ -405,7 +419,13 @@ CREATE TABLE "_WorkflowToWorkflowTag" ( ); -- CreateIndex -CREATE UNIQUE INDEX "Action_tenantId_id_key" ON "Action"("tenantId" ASC, "id" ASC); +CREATE UNIQUE INDEX "APIToken_id_key" ON "APIToken"("id" ASC); + +-- CreateIndex +CREATE UNIQUE INDEX "Action_id_key" ON "Action"("id" ASC); + +-- CreateIndex +CREATE UNIQUE INDEX "Action_tenantId_actionId_key" ON "Action"("tenantId" ASC, "actionId" ASC); -- CreateIndex CREATE UNIQUE INDEX "Dispatcher_id_key" ON "Dispatcher"("id" ASC); @@ -566,6 +586,9 @@ CREATE UNIQUE INDEX "_WorkflowToWorkflowTag_AB_unique" ON "_WorkflowToWorkflowTa -- CreateIndex CREATE INDEX "_WorkflowToWorkflowTag_B_index" ON "_WorkflowToWorkflowTag"("B" ASC); +-- AddForeignKey +ALTER TABLE "APIToken" ADD CONSTRAINT "APIToken_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE; + -- AddForeignKey ALTER TABLE "Action" ADD CONSTRAINT "Action_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE; @@ -603,7 +626,7 @@ ALTER TABLE "JobRunLookupData" ADD CONSTRAINT "JobRunLookupData_tenantId_fkey" F ALTER TABLE "Service" ADD CONSTRAINT "Service_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "Step" ADD CONSTRAINT "Step_actionId_tenantId_fkey" FOREIGN KEY ("actionId", "tenantId") REFERENCES "Action"("id", "tenantId") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "Step" ADD CONSTRAINT "Step_actionId_tenantId_fkey" FOREIGN KEY ("actionId", "tenantId") REFERENCES "Action"("actionId", "tenantId") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "Step" ADD CONSTRAINT "Step_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "Job"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/internal/repository/prisma/dbsqlc/workflows.sql b/internal/repository/prisma/dbsqlc/workflows.sql index 281ecf613..2875d6da8 100644 --- a/internal/repository/prisma/dbsqlc/workflows.sql +++ b/internal/repository/prisma/dbsqlc/workflows.sql @@ -222,17 +222,19 @@ JOIN -- name: UpsertAction :exec INSERT INTO "Action" ( "id", + "actionId", "tenantId" ) VALUES ( + gen_random_uuid(), @action::text, @tenantId::uuid ) -ON CONFLICT ("tenantId", "id") DO UPDATE +ON CONFLICT ("tenantId", "actionId") DO UPDATE SET "tenantId" = EXCLUDED."tenantId" WHERE - "Action"."tenantId" = @tenantId AND "Action"."id" = @action; + "Action"."tenantId" = @tenantId AND "Action"."actionId" = @action::text; -- name: UpsertWorkflowTag :exec INSERT INTO "WorkflowTag" ( diff --git a/internal/repository/prisma/dbsqlc/workflows.sql.go b/internal/repository/prisma/dbsqlc/workflows.sql.go index 00f9f4206..2f4ca5b39 100644 --- a/internal/repository/prisma/dbsqlc/workflows.sql.go +++ b/internal/repository/prisma/dbsqlc/workflows.sql.go @@ -628,17 +628,19 @@ func (q *Queries) ListWorkflowsLatestRuns(ctx context.Context, db DBTX, arg List const upsertAction = `-- name: UpsertAction :exec INSERT INTO "Action" ( "id", + "actionId", "tenantId" ) VALUES ( + gen_random_uuid(), $1::text, $2::uuid ) -ON CONFLICT ("tenantId", "id") DO UPDATE +ON CONFLICT ("tenantId", "actionId") DO UPDATE SET "tenantId" = EXCLUDED."tenantId" WHERE - "Action"."tenantId" = $2 AND "Action"."id" = $1 + "Action"."tenantId" = $2 AND "Action"."actionId" = $1::text ` type UpsertActionParams struct { diff --git a/internal/repository/prisma/worker.go b/internal/repository/prisma/worker.go index 6cbc1fb58..b17b44388 100644 --- a/internal/repository/prisma/worker.go +++ b/internal/repository/prisma/worker.go @@ -68,7 +68,7 @@ func (w *workerRepository) ListWorkers(tenantId string, opts *repository.ListWor if opts.Action != nil { queryParams = append(queryParams, db.Worker.Actions.Some( db.Action.TenantID.Equals(tenantId), - db.Action.ID.Equals(*opts.Action), + db.Action.ActionID.Equals(*opts.Action), )) } @@ -203,12 +203,12 @@ func (w *workerRepository) CreateNewWorker(tenantId string, opts *repository.Cre if len(opts.Actions) > 0 { for _, action := range opts.Actions { txs = append(txs, w.client.Action.UpsertOne( - db.Action.TenantIDID( + db.Action.TenantIDActionID( db.Action.TenantID.Equals(tenantId), - db.Action.ID.Equals(action), + db.Action.ActionID.Equals(action), ), ).Create( - db.Action.ID.Set(action), + db.Action.ActionID.Set(action), db.Action.Tenant.Link( db.Tenant.ID.Equals(tenantId), ), @@ -221,9 +221,9 @@ func (w *workerRepository) CreateNewWorker(tenantId string, opts *repository.Cre db.Worker.ID.Equals(workerId), ).Update( db.Worker.Actions.Link( - db.Action.TenantIDID( + db.Action.TenantIDActionID( db.Action.TenantID.Equals(tenantId), - db.Action.ID.Equals(action), + db.Action.ActionID.Equals(action), ), ), ).Tx()) @@ -265,12 +265,12 @@ func (w *workerRepository) UpdateWorker(tenantId, workerId string, opts *reposit if len(opts.Actions) > 0 { for _, action := range opts.Actions { txs = append(txs, w.client.Action.UpsertOne( - db.Action.TenantIDID( + db.Action.TenantIDActionID( db.Action.TenantID.Equals(tenantId), - db.Action.ID.Equals(action), + db.Action.ActionID.Equals(action), ), ).Create( - db.Action.ID.Set(action), + db.Action.ActionID.Set(action), db.Action.Tenant.Link( db.Tenant.ID.Equals(tenantId), ), @@ -283,9 +283,9 @@ func (w *workerRepository) UpdateWorker(tenantId, workerId string, opts *reposit db.Worker.ID.Equals(workerId), ).Update( db.Worker.Actions.Link( - db.Action.TenantIDID( + db.Action.TenantIDActionID( db.Action.TenantID.Equals(tenantId), - db.Action.ID.Equals(action), + db.Action.ActionID.Equals(action), ), ), ).Tx()) diff --git a/internal/services/admin/contracts/workflows.pb.go b/internal/services/admin/contracts/workflows.pb.go index 652a44af4..42643a0df 100644 --- a/internal/services/admin/contracts/workflows.pb.go +++ b/internal/services/admin/contracts/workflows.pb.go @@ -27,8 +27,7 @@ type PutWorkflowRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` - Opts *CreateWorkflowVersionOpts `protobuf:"bytes,2,opt,name=opts,proto3" json:"opts,omitempty"` + Opts *CreateWorkflowVersionOpts `protobuf:"bytes,1,opt,name=opts,proto3" json:"opts,omitempty"` } func (x *PutWorkflowRequest) Reset() { @@ -63,13 +62,6 @@ func (*PutWorkflowRequest) Descriptor() ([]byte, []int) { return file_workflows_proto_rawDescGZIP(), []int{0} } -func (x *PutWorkflowRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *PutWorkflowRequest) GetOpts() *CreateWorkflowVersionOpts { if x != nil { return x.Opts @@ -330,8 +322,6 @@ type ListWorkflowsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - - TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` } func (x *ListWorkflowsRequest) Reset() { @@ -366,23 +356,15 @@ func (*ListWorkflowsRequest) Descriptor() ([]byte, []int) { return file_workflows_proto_rawDescGZIP(), []int{4} } -func (x *ListWorkflowsRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - type ScheduleWorkflowRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` - WorkflowId string `protobuf:"bytes,2,opt,name=workflow_id,json=workflowId,proto3" json:"workflow_id,omitempty"` - Schedules []*timestamppb.Timestamp `protobuf:"bytes,3,rep,name=schedules,proto3" json:"schedules,omitempty"` + WorkflowId string `protobuf:"bytes,1,opt,name=workflow_id,json=workflowId,proto3" json:"workflow_id,omitempty"` + Schedules []*timestamppb.Timestamp `protobuf:"bytes,2,rep,name=schedules,proto3" json:"schedules,omitempty"` // (optional) the input data for the workflow - Input string `protobuf:"bytes,4,opt,name=input,proto3" json:"input,omitempty"` + Input string `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"` } func (x *ScheduleWorkflowRequest) Reset() { @@ -417,13 +399,6 @@ func (*ScheduleWorkflowRequest) Descriptor() ([]byte, []int) { return file_workflows_proto_rawDescGZIP(), []int{5} } -func (x *ScheduleWorkflowRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *ScheduleWorkflowRequest) GetWorkflowId() string { if x != nil { return x.WorkflowId @@ -499,8 +474,7 @@ type ListWorkflowsForEventRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` - EventKey string `protobuf:"bytes,2,opt,name=event_key,json=eventKey,proto3" json:"event_key,omitempty"` + EventKey string `protobuf:"bytes,1,opt,name=event_key,json=eventKey,proto3" json:"event_key,omitempty"` } func (x *ListWorkflowsForEventRequest) Reset() { @@ -535,13 +509,6 @@ func (*ListWorkflowsForEventRequest) Descriptor() ([]byte, []int) { return file_workflows_proto_rawDescGZIP(), []int{7} } -func (x *ListWorkflowsForEventRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *ListWorkflowsForEventRequest) GetEventKey() string { if x != nil { return x.EventKey @@ -1194,8 +1161,7 @@ type DeleteWorkflowRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` - WorkflowId string `protobuf:"bytes,2,opt,name=workflow_id,json=workflowId,proto3" json:"workflow_id,omitempty"` + WorkflowId string `protobuf:"bytes,1,opt,name=workflow_id,json=workflowId,proto3" json:"workflow_id,omitempty"` } func (x *DeleteWorkflowRequest) Reset() { @@ -1230,13 +1196,6 @@ func (*DeleteWorkflowRequest) Descriptor() ([]byte, []int) { return file_workflows_proto_rawDescGZIP(), []int{15} } -func (x *DeleteWorkflowRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *DeleteWorkflowRequest) GetWorkflowId() string { if x != nil { return x.WorkflowId @@ -1249,8 +1208,7 @@ type GetWorkflowByNameRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } func (x *GetWorkflowByNameRequest) Reset() { @@ -1285,13 +1243,6 @@ func (*GetWorkflowByNameRequest) Descriptor() ([]byte, []int) { return file_workflows_proto_rawDescGZIP(), []int{16} } -func (x *GetWorkflowByNameRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *GetWorkflowByNameRequest) GetName() string { if x != nil { return x.Name @@ -1307,145 +1258,108 @@ var file_workflows_proto_rawDesc = []byte{ 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0x61, 0x0a, 0x12, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x6f, 0x70, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4f, 0x70, 0x74, 0x73, 0x52, - 0x04, 0x6f, 0x70, 0x74, 0x73, 0x22, 0xae, 0x02, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4f, + 0x74, 0x6f, 0x22, 0x44, 0x0a, 0x12, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6f, 0x70, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4f, 0x70, + 0x74, 0x73, 0x52, 0x04, 0x6f, 0x70, 0x74, 0x73, 0x22, 0xae, 0x02, 0x0a, 0x19, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, + 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x23, 0x0a, + 0x0d, 0x63, 0x72, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x72, 0x6f, 0x6e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, + 0x72, 0x73, 0x12, 0x49, 0x0a, 0x12, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x5f, + 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x11, 0x73, 0x63, 0x68, 0x65, + 0x64, 0x75, 0x6c, 0x65, 0x64, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, + 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x4f, + 0x70, 0x74, 0x73, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x15, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x72, 0x69, - 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, - 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0c, 0x63, 0x72, 0x6f, 0x6e, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, - 0x49, 0x0a, 0x12, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x69, - 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x11, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, - 0x65, 0x64, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x04, 0x6a, 0x6f, - 0x62, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x4f, 0x70, 0x74, 0x73, - 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4a, 0x6f, 0x62, 0x4f, 0x70, 0x74, 0x73, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, + 0x6f, 0x75, 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x65, 0x70, 0x4f, 0x70, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x65, + 0x70, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x74, 0x65, 0x70, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x1f, 0x0a, + 0x0b, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x53, 0x74, 0x65, 0x70, 0x4f, 0x70, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x22, - 0x9d, 0x01, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x53, 0x74, 0x65, 0x70, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, - 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, - 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, - 0x33, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x49, 0x64, 0x22, 0xa7, 0x01, 0x0a, 0x17, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, - 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, - 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, 0x38, - 0x0a, 0x09, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, - 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x22, 0x40, - 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x57, 0x6f, 0x72, - 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, - 0x22, 0x58, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x73, 0x46, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, - 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x22, 0xaf, 0x02, 0x0a, 0x08, 0x57, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8a, 0x01, 0x0a, 0x17, 0x53, + 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x73, 0x63, 0x68, 0x65, 0x64, + 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x22, 0x40, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x27, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x09, + 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x22, 0x3b, 0x0a, 0x1c, 0x4c, 0x69, 0x73, + 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x22, 0xaf, 0x02, 0x0a, 0x08, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, + 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, + 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x08, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xb1, 0x02, 0x0a, 0x0f, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, - 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3e, - 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, - 0x0a, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x10, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xb1, 0x02, 0x0a, - 0x0f, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, - 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x08, 0x74, 0x72, 0x69, 0x67, 0x67, - 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x52, 0x08, 0x74, 0x72, - 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x09, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, - 0x22, 0xc6, 0x02, 0x0a, 0x10, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x69, - 0x67, 0x67, 0x65, 0x72, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x77, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x66, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2d, 0x0a, 0x05, 0x63, 0x72, - 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x72, 0x6f, 0x6e, 0x52, - 0x65, 0x66, 0x52, 0x05, 0x63, 0x72, 0x6f, 0x6e, 0x73, 0x22, 0x53, 0x0a, 0x17, 0x57, 0x6f, 0x72, - 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x66, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x22, 0x49, - 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, - 0x72, 0x43, 0x72, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x72, - 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x22, 0x81, 0x03, 0x0a, 0x03, 0x4a, 0x6f, - 0x62, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x08, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x73, 0x52, 0x08, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, + 0x72, 0x73, 0x12, 0x18, 0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0xc6, 0x02, 0x0a, + 0x10, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, + 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, @@ -1453,86 +1367,113 @@ var file_workflows_proto_rawDesc = []byte{ 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x11, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, + 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x66, 0x52, 0x06, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2d, 0x0a, 0x05, 0x63, 0x72, 0x6f, 0x6e, 0x73, 0x18, + 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x72, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x52, 0x05, + 0x63, 0x72, 0x6f, 0x6e, 0x73, 0x22, 0x53, 0x0a, 0x17, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x66, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x22, 0x49, 0x0a, 0x16, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x43, 0x72, 0x6f, + 0x6e, 0x52, 0x65, 0x66, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x22, 0x81, 0x03, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, + 0x12, 0x2e, 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x77, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x05, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, 0x09, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, 0x05, 0x73, 0x74, 0x65, 0x70, + 0x73, 0x12, 0x36, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x85, 0x03, 0x0a, 0x04, 0x53, 0x74, + 0x65, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, + 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3d, 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x64, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x05, 0x73, 0x74, 0x65, 0x70, - 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x52, 0x05, - 0x73, 0x74, 0x65, 0x70, 0x73, 0x12, 0x36, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x85, 0x03, - 0x0a, 0x04, 0x53, 0x74, 0x65, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3d, 0x0a, 0x0b, - 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, - 0x0a, 0x72, 0x65, 0x61, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, - 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, - 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, - 0x75, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, - 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x07, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x68, 0x69, - 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x69, - 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x22, 0x55, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, - 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x77, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, 0x4b, 0x0a, 0x18, - 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x42, 0x79, 0x4e, 0x61, 0x6d, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x32, 0x87, 0x03, 0x0a, 0x0f, 0x57, 0x6f, - 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, - 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x12, 0x15, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, - 0x0b, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x13, 0x2e, 0x50, - 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x10, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x10, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x57, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x18, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, - 0x6c, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x10, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, - 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x4e, - 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x46, - 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, - 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, - 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, - 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x12, 0x16, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, - 0x74, 0x63, 0x68, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x72, 0x65, 0x61, + 0x64, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, + 0x72, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, + 0x6e, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, + 0x6e, 0x22, 0x38, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, 0x2e, 0x0a, 0x18, 0x47, + 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x32, 0x87, 0x03, 0x0a, 0x0f, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x3e, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, + 0x12, 0x15, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x34, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x13, + 0x2e, 0x50, 0x75, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x10, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, + 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x18, 0x2e, 0x53, 0x63, 0x68, 0x65, + 0x64, 0x75, 0x6c, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x2e, 0x47, 0x65, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x12, 0x4e, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x73, 0x46, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x33, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x12, 0x16, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, + 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/internal/services/admin/server.go b/internal/services/admin/server.go index a83159748..c722795e0 100644 --- a/internal/services/admin/server.go +++ b/internal/services/admin/server.go @@ -22,8 +22,10 @@ import ( ) func (a *AdminServiceImpl) GetWorkflowByName(ctx context.Context, req *contracts.GetWorkflowByNameRequest) (*contracts.Workflow, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + workflow, err := a.repo.Workflow().GetWorkflowByName( - req.TenantId, + tenant.ID, req.Name, ) @@ -44,7 +46,7 @@ func (a *AdminServiceImpl) GetWorkflowByName(ctx context.Context, req *contracts } func (a *AdminServiceImpl) PutWorkflow(ctx context.Context, req *contracts.PutWorkflowRequest) (*contracts.WorkflowVersion, error) { - // TODO: authorize request + tenant := ctx.Value("tenant").(*db.TenantModel) createOpts, err := getCreateWorkflowOpts(req) @@ -57,7 +59,7 @@ func (a *AdminServiceImpl) PutWorkflow(ctx context.Context, req *contracts.PutWo var oldWorkflowVersion *db.WorkflowVersionModel currWorkflow, err := a.repo.Workflow().GetWorkflowByName( - req.TenantId, + tenant.ID, req.Opts.Name, ) @@ -68,7 +70,7 @@ func (a *AdminServiceImpl) PutWorkflow(ctx context.Context, req *contracts.PutWo // workflow does not exist, create it workflowVersion, err = a.repo.Workflow().CreateNewWorkflow( - req.TenantId, + tenant.ID, createOpts, ) @@ -80,7 +82,7 @@ func (a *AdminServiceImpl) PutWorkflow(ctx context.Context, req *contracts.PutWo // workflow exists, create a new version workflowVersion, err = a.repo.Workflow().CreateWorkflowVersion( - req.TenantId, + tenant.ID, createOpts, ) @@ -295,7 +297,7 @@ func (a *AdminServiceImpl) PutWorkflow(ctx context.Context, req *contracts.PutWo } func (a *AdminServiceImpl) ScheduleWorkflow(ctx context.Context, req *contracts.ScheduleWorkflowRequest) (*contracts.WorkflowVersion, error) { - // TODO: authorize request + tenant := ctx.Value("tenant").(*db.TenantModel) currWorkflow, err := a.repo.Workflow().GetWorkflowById( req.WorkflowId, @@ -336,7 +338,7 @@ func (a *AdminServiceImpl) ScheduleWorkflow(ctx context.Context, req *contracts. } schedules, err := a.repo.Workflow().CreateSchedules( - req.TenantId, + tenant.ID, workflowVersion.ID, &repository.CreateWorkflowSchedulesOpts{ ScheduledTriggers: dbSchedules, @@ -416,8 +418,10 @@ func (a *AdminServiceImpl) ScheduleWorkflow(ctx context.Context, req *contracts. } func (a *AdminServiceImpl) DeleteWorkflow(ctx context.Context, req *contracts.DeleteWorkflowRequest) (*contracts.Workflow, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + workflow, err := a.repo.Workflow().DeleteWorkflow( - req.TenantId, + tenant.ID, req.WorkflowId, ) @@ -434,8 +438,10 @@ func (a *AdminServiceImpl) ListWorkflows( ctx context.Context, req *contracts.ListWorkflowsRequest, ) (*contracts.ListWorkflowsResponse, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + listResp, err := a.repo.Workflow().ListWorkflows( - req.TenantId, + tenant.ID, &repository.ListWorkflowsOpts{}, ) @@ -458,8 +464,10 @@ func (a *AdminServiceImpl) ListWorkflowsForEvent( ctx context.Context, req *contracts.ListWorkflowsForEventRequest, ) (*contracts.ListWorkflowsResponse, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + listResp, err := a.repo.Workflow().ListWorkflows( - req.TenantId, + tenant.ID, &repository.ListWorkflowsOpts{ EventKey: &req.EventKey, }, diff --git a/internal/services/dispatcher/contracts/dispatcher.pb.go b/internal/services/dispatcher/contracts/dispatcher.pb.go index a61257f3a..6c8161afe 100644 --- a/internal/services/dispatcher/contracts/dispatcher.pb.go +++ b/internal/services/dispatcher/contracts/dispatcher.pb.go @@ -124,14 +124,12 @@ type WorkerRegisterRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // the tenant id - TenantId string `protobuf:"bytes,1,opt,name=tenantId,proto3" json:"tenantId,omitempty"` // the name of the worker - WorkerName string `protobuf:"bytes,2,opt,name=workerName,proto3" json:"workerName,omitempty"` + WorkerName string `protobuf:"bytes,1,opt,name=workerName,proto3" json:"workerName,omitempty"` // a list of actions that this worker can run - Actions []string `protobuf:"bytes,3,rep,name=actions,proto3" json:"actions,omitempty"` + Actions []string `protobuf:"bytes,2,rep,name=actions,proto3" json:"actions,omitempty"` // (optional) the services for this worker - Services []string `protobuf:"bytes,4,rep,name=services,proto3" json:"services,omitempty"` + Services []string `protobuf:"bytes,3,rep,name=services,proto3" json:"services,omitempty"` } func (x *WorkerRegisterRequest) Reset() { @@ -166,13 +164,6 @@ func (*WorkerRegisterRequest) Descriptor() ([]byte, []int) { return file_dispatcher_proto_rawDescGZIP(), []int{0} } -func (x *WorkerRegisterRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *WorkerRegisterRequest) GetWorkerName() string { if x != nil { return x.WorkerName @@ -385,10 +376,8 @@ type WorkerListenRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // the tenant id - TenantId string `protobuf:"bytes,1,opt,name=tenantId,proto3" json:"tenantId,omitempty"` // the id of the worker - WorkerId string `protobuf:"bytes,2,opt,name=workerId,proto3" json:"workerId,omitempty"` + WorkerId string `protobuf:"bytes,1,opt,name=workerId,proto3" json:"workerId,omitempty"` } func (x *WorkerListenRequest) Reset() { @@ -423,13 +412,6 @@ func (*WorkerListenRequest) Descriptor() ([]byte, []int) { return file_dispatcher_proto_rawDescGZIP(), []int{3} } -func (x *WorkerListenRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *WorkerListenRequest) GetWorkerId() string { if x != nil { return x.WorkerId @@ -442,10 +424,8 @@ type WorkerUnsubscribeRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // the tenant id to unsubscribe from - TenantId string `protobuf:"bytes,1,opt,name=tenantId,proto3" json:"tenantId,omitempty"` // the id of the worker - WorkerId string `protobuf:"bytes,2,opt,name=workerId,proto3" json:"workerId,omitempty"` + WorkerId string `protobuf:"bytes,1,opt,name=workerId,proto3" json:"workerId,omitempty"` } func (x *WorkerUnsubscribeRequest) Reset() { @@ -480,13 +460,6 @@ func (*WorkerUnsubscribeRequest) Descriptor() ([]byte, []int) { return file_dispatcher_proto_rawDescGZIP(), []int{4} } -func (x *WorkerUnsubscribeRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *WorkerUnsubscribeRequest) GetWorkerId() string { if x != nil { return x.WorkerId @@ -556,25 +529,23 @@ type ActionEvent struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // the tenant id - TenantId string `protobuf:"bytes,1,opt,name=tenantId,proto3" json:"tenantId,omitempty"` // the id of the worker - WorkerId string `protobuf:"bytes,2,opt,name=workerId,proto3" json:"workerId,omitempty"` + WorkerId string `protobuf:"bytes,1,opt,name=workerId,proto3" json:"workerId,omitempty"` // the id of the job - JobId string `protobuf:"bytes,3,opt,name=jobId,proto3" json:"jobId,omitempty"` + JobId string `protobuf:"bytes,2,opt,name=jobId,proto3" json:"jobId,omitempty"` // the job run id - JobRunId string `protobuf:"bytes,4,opt,name=jobRunId,proto3" json:"jobRunId,omitempty"` + JobRunId string `protobuf:"bytes,3,opt,name=jobRunId,proto3" json:"jobRunId,omitempty"` // the id of the step - StepId string `protobuf:"bytes,5,opt,name=stepId,proto3" json:"stepId,omitempty"` + StepId string `protobuf:"bytes,4,opt,name=stepId,proto3" json:"stepId,omitempty"` // the step run id - StepRunId string `protobuf:"bytes,6,opt,name=stepRunId,proto3" json:"stepRunId,omitempty"` + StepRunId string `protobuf:"bytes,5,opt,name=stepRunId,proto3" json:"stepRunId,omitempty"` // the action id - ActionId string `protobuf:"bytes,7,opt,name=actionId,proto3" json:"actionId,omitempty"` - EventTimestamp *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=eventTimestamp,proto3" json:"eventTimestamp,omitempty"` + ActionId string `protobuf:"bytes,6,opt,name=actionId,proto3" json:"actionId,omitempty"` + EventTimestamp *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=eventTimestamp,proto3" json:"eventTimestamp,omitempty"` // the step event type - EventType ActionEventType `protobuf:"varint,9,opt,name=eventType,proto3,enum=ActionEventType" json:"eventType,omitempty"` + EventType ActionEventType `protobuf:"varint,8,opt,name=eventType,proto3,enum=ActionEventType" json:"eventType,omitempty"` // the event payload - EventPayload string `protobuf:"bytes,10,opt,name=eventPayload,proto3" json:"eventPayload,omitempty"` + EventPayload string `protobuf:"bytes,9,opt,name=eventPayload,proto3" json:"eventPayload,omitempty"` } func (x *ActionEvent) Reset() { @@ -609,13 +580,6 @@ func (*ActionEvent) Descriptor() ([]byte, []int) { return file_dispatcher_proto_rawDescGZIP(), []int{6} } -func (x *ActionEvent) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *ActionEvent) GetWorkerId() string { if x != nil { return x.WorkerId @@ -742,117 +706,109 @@ var file_dispatcher_proto_rawDesc = []byte{ 0x0a, 0x10, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0x89, 0x01, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, - 0x70, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, - 0x65, 0x22, 0x9d, 0x02, 0x0a, 0x0e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, - 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6a, 0x6f, 0x62, 0x4e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x65, 0x70, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, - 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b, - 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, - 0x64, 0x22, 0x4d, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x65, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, - 0x22, 0x52, 0x0a, 0x18, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x6f, 0x74, 0x6f, 0x22, 0x6d, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, + 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x22, 0x70, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, - 0x65, 0x72, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x19, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e, - 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, - 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x22, 0xe1, 0x02, 0x0a, 0x0b, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, - 0x6e, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, - 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, - 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x73, 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x42, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2e, 0x0a, 0x09, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x41, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x4d, 0x0a, - 0x13, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, - 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x2a, 0x35, 0x0a, 0x0a, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, - 0x41, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x52, 0x55, 0x4e, 0x10, 0x00, 0x12, 0x13, - 0x0a, 0x0f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x52, 0x55, - 0x4e, 0x10, 0x01, 0x2a, 0x86, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x45, 0x50, 0x5f, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, - 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, - 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x02, - 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x32, 0x81, 0x02, 0x0a, - 0x0a, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x08, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x16, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x17, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x06, 0x4c, 0x69, - 0x73, 0x74, 0x65, 0x6e, 0x12, 0x14, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x41, 0x73, 0x73, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x30, 0x01, 0x12, - 0x37, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x0c, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x1a, 0x14, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0b, 0x55, 0x6e, 0x73, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x19, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, - 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x42, 0x47, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, - 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, - 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x9d, 0x02, 0x0a, 0x0e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6a, 0x6f, 0x62, + 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x65, 0x70, 0x52, + 0x75, 0x6e, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x65, 0x70, + 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x12, 0x2b, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, + 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, + 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x31, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x69, + 0x73, 0x74, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x22, 0x36, 0x0a, 0x18, 0x57, 0x6f, 0x72, 0x6b, 0x65, + 0x72, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x22, + 0x53, 0x0a, 0x19, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x49, 0x64, 0x22, 0xc5, 0x02, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, + 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x6f, 0x62, 0x52, 0x75, 0x6e, + 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x65, 0x70, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, + 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, + 0x74, 0x65, 0x70, 0x52, 0x75, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x42, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2e, 0x0a, 0x09, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x4d, 0x0a, 0x13, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, + 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x2a, 0x35, 0x0a, 0x0a, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, + 0x52, 0x54, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x52, 0x55, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, + 0x0f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x5f, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x52, 0x55, 0x4e, + 0x10, 0x01, 0x2a, 0x86, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, + 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, 0x4e, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x01, + 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, + 0x1a, 0x0a, 0x16, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x32, 0x81, 0x02, 0x0a, 0x0a, + 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x08, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x16, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, + 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x06, 0x4c, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x12, 0x14, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, + 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x41, 0x73, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x30, 0x01, 0x12, 0x37, + 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x0c, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x1a, + 0x14, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0b, 0x55, 0x6e, 0x73, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x19, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, + 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1a, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x47, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, + 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, + 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/services/dispatcher/server.go b/internal/services/dispatcher/server.go index c1ae637fd..6a709ca18 100644 --- a/internal/services/dispatcher/server.go +++ b/internal/services/dispatcher/server.go @@ -90,7 +90,7 @@ func (worker *subscribedWorker) CancelStepRun( } func (s *DispatcherImpl) Register(ctx context.Context, request *contracts.WorkerRegisterRequest) (*contracts.WorkerRegisterResponse, error) { - // TODO: auth checks to make sure the worker is allowed to register for this tenant + tenant := ctx.Value("tenant").(*db.TenantModel) s.l.Debug().Msgf("Received register request from ID %s with actions %v", request.WorkerName, request.Actions) @@ -101,7 +101,7 @@ func (s *DispatcherImpl) Register(ctx context.Context, request *contracts.Worker } // create a worker in the database - worker, err := s.repo.Worker().CreateNewWorker(request.TenantId, &repository.CreateWorkerOpts{ + worker, err := s.repo.Worker().CreateNewWorker(tenant.ID, &repository.CreateWorkerOpts{ DispatcherId: s.dispatcherId, Name: request.WorkerName, Actions: request.Actions, @@ -109,7 +109,7 @@ func (s *DispatcherImpl) Register(ctx context.Context, request *contracts.Worker }) if err != nil { - s.l.Error().Err(err).Msgf("could not create worker for tenant %s", request.TenantId) + s.l.Error().Err(err).Msgf("could not create worker for tenant %s", tenant.ID) return nil, err } @@ -125,6 +125,8 @@ func (s *DispatcherImpl) Register(ctx context.Context, request *contracts.Worker // Subscribe handles a subscribe request from a client func (s *DispatcherImpl) Listen(request *contracts.WorkerListenRequest, stream contracts.Dispatcher_ListenServer) error { + tenant := stream.Context().Value("tenant").(*db.TenantModel) + s.l.Debug().Msgf("Received subscribe request from ID: %s", request.WorkerId) worker, err := s.repo.Worker().GetWorkerById(request.WorkerId) @@ -136,7 +138,7 @@ func (s *DispatcherImpl) Listen(request *contracts.WorkerListenRequest, stream c // check the worker's dispatcher against the current dispatcher. if they don't match, then update the worker if worker.DispatcherID != s.dispatcherId { - _, err = s.repo.Worker().UpdateWorker(request.TenantId, request.WorkerId, &repository.UpdateWorkerOpts{ + _, err = s.repo.Worker().UpdateWorker(tenant.ID, request.WorkerId, &repository.UpdateWorkerOpts{ DispatcherId: &s.dispatcherId, }) @@ -155,7 +157,7 @@ func (s *DispatcherImpl) Listen(request *contracts.WorkerListenRequest, stream c inactive := db.WorkerStatusInactive - _, err := s.repo.Worker().UpdateWorker(request.TenantId, request.WorkerId, &repository.UpdateWorkerOpts{ + _, err := s.repo.Worker().UpdateWorker(tenant.ID, request.WorkerId, &repository.UpdateWorkerOpts{ Status: &inactive, }) @@ -183,7 +185,7 @@ func (s *DispatcherImpl) Listen(request *contracts.WorkerListenRequest, stream c if now := time.Now().UTC(); lastHeartbeat.Add(5 * time.Second).Before(now) { s.l.Debug().Msgf("updating worker %s heartbeat", request.WorkerId) - _, err := s.repo.Worker().UpdateWorker(request.TenantId, request.WorkerId, &repository.UpdateWorkerOpts{ + _, err := s.repo.Worker().UpdateWorker(tenant.ID, request.WorkerId, &repository.UpdateWorkerOpts{ LastHeartbeatAt: &now, }) @@ -227,23 +229,26 @@ func (s *DispatcherImpl) SendActionEvent(ctx context.Context, request *contracts } func (s *DispatcherImpl) Unsubscribe(ctx context.Context, request *contracts.WorkerUnsubscribeRequest) (*contracts.WorkerUnsubscribeResponse, error) { - // TODO: auth checks to make sure the worker is allowed to unsubscribe for this tenant + tenant := ctx.Value("tenant").(*db.TenantModel) + // no matter what, remove the worker from the connection pool defer s.workers.Delete(request.WorkerId) - err := s.repo.Worker().DeleteWorker(request.TenantId, request.WorkerId) + err := s.repo.Worker().DeleteWorker(tenant.ID, request.WorkerId) if err != nil { return nil, err } return &contracts.WorkerUnsubscribeResponse{ - TenantId: request.TenantId, + TenantId: tenant.ID, WorkerId: request.WorkerId, }, nil } func (s *DispatcherImpl) handleStepRunStarted(ctx context.Context, request *contracts.ActionEvent) (*contracts.ActionEventResponse, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + s.l.Debug().Msgf("Received step started event for step run %s", request.StepRunId) startedAt := request.EventTimestamp.AsTime() @@ -254,7 +259,7 @@ func (s *DispatcherImpl) handleStepRunStarted(ctx context.Context, request *cont }) metadata, _ := datautils.ToJSONMap(tasktypes.StepRunStartedTaskMetadata{ - TenantId: request.TenantId, + TenantId: tenant.ID, }) // send the event to the jobs queue @@ -270,12 +275,14 @@ func (s *DispatcherImpl) handleStepRunStarted(ctx context.Context, request *cont } return &contracts.ActionEventResponse{ - TenantId: request.TenantId, + TenantId: tenant.ID, WorkerId: request.WorkerId, }, nil } func (s *DispatcherImpl) handleStepRunCompleted(ctx context.Context, request *contracts.ActionEvent) (*contracts.ActionEventResponse, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + s.l.Debug().Msgf("Received step completed event for step run %s", request.StepRunId) finishedAt := request.EventTimestamp.AsTime() @@ -287,7 +294,7 @@ func (s *DispatcherImpl) handleStepRunCompleted(ctx context.Context, request *co }) metadata, _ := datautils.ToJSONMap(tasktypes.StepRunFinishedTaskMetadata{ - TenantId: request.TenantId, + TenantId: tenant.ID, }) // send the event to the jobs queue @@ -303,12 +310,14 @@ func (s *DispatcherImpl) handleStepRunCompleted(ctx context.Context, request *co } return &contracts.ActionEventResponse{ - TenantId: request.TenantId, + TenantId: tenant.ID, WorkerId: request.WorkerId, }, nil } func (s *DispatcherImpl) handleStepRunFailed(ctx context.Context, request *contracts.ActionEvent) (*contracts.ActionEventResponse, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + s.l.Debug().Msgf("Received step failed event for step run %s", request.StepRunId) failedAt := request.EventTimestamp.AsTime() @@ -320,7 +329,7 @@ func (s *DispatcherImpl) handleStepRunFailed(ctx context.Context, request *contr }) metadata, _ := datautils.ToJSONMap(tasktypes.StepRunFailedTaskMetadata{ - TenantId: request.TenantId, + TenantId: tenant.ID, }) // send the event to the jobs queue @@ -336,7 +345,7 @@ func (s *DispatcherImpl) handleStepRunFailed(ctx context.Context, request *contr } return &contracts.ActionEventResponse{ - TenantId: request.TenantId, + TenantId: tenant.ID, WorkerId: request.WorkerId, }, nil } diff --git a/internal/services/grpc/middleware/auth.go b/internal/services/grpc/middleware/auth.go new file mode 100644 index 000000000..00f99253b --- /dev/null +++ b/internal/services/grpc/middleware/auth.go @@ -0,0 +1,53 @@ +package middleware + +import ( + "context" + + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" + "github.com/rs/zerolog" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/hatchet-dev/hatchet/internal/config/server" +) + +type GRPCAuthN struct { + config *server.ServerConfig + + l *zerolog.Logger +} + +func NewAuthN(config *server.ServerConfig) *GRPCAuthN { + return &GRPCAuthN{ + config: config, + l: config.Logger, + } +} + +func (a *GRPCAuthN) Middleware(ctx context.Context) (context.Context, error) { + forbidden := status.Errorf(codes.Unauthenticated, "invalid auth token") + token, err := auth.AuthFromMD(ctx, "bearer") + + if err != nil { + a.l.Debug().Err(err).Msg("error getting bearer token from request") + return nil, forbidden + } + + tenantId, err := a.config.Auth.JWTManager.ValidateTenantToken(token) + + if err != nil { + a.l.Debug().Err(err).Msg("error validating tenant token") + + return nil, forbidden + } + + // get the tenant id + queriedTenant, err := a.config.Repository.Tenant().GetTenantByID(tenantId) + + if err != nil { + a.l.Debug().Err(err).Msg("error getting tenant by id") + return nil, forbidden + } + + return context.WithValue(ctx, "tenant", queriedTenant), nil +} diff --git a/internal/services/grpc/server.go b/internal/services/grpc/server.go index 3344c19fc..e194bd783 100644 --- a/internal/services/grpc/server.go +++ b/internal/services/grpc/server.go @@ -6,16 +6,19 @@ import ( "fmt" "net" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" + "github.com/hatchet-dev/hatchet/internal/config/server" "github.com/hatchet-dev/hatchet/internal/logger" "github.com/hatchet-dev/hatchet/internal/services/admin" admincontracts "github.com/hatchet-dev/hatchet/internal/services/admin/contracts" "github.com/hatchet-dev/hatchet/internal/services/dispatcher" dispatchercontracts "github.com/hatchet-dev/hatchet/internal/services/dispatcher/contracts" + "github.com/hatchet-dev/hatchet/internal/services/grpc/middleware" "github.com/hatchet-dev/hatchet/internal/services/ingestor" eventcontracts "github.com/hatchet-dev/hatchet/internal/services/ingestor/contracts" ) @@ -29,6 +32,7 @@ type Server struct { port int bindAddress string + config *server.ServerConfig ingestor ingestor.Ingestor dispatcher dispatcher.Dispatcher admin admin.AdminService @@ -39,6 +43,7 @@ type Server struct { type ServerOpt func(*ServerOpts) type ServerOpts struct { + config *server.ServerConfig l *zerolog.Logger port int bindAddress string @@ -84,6 +89,12 @@ func WithIngestor(i ingestor.Ingestor) ServerOpt { } } +func WithConfig(config *server.ServerConfig) ServerOpt { + return func(opts *ServerOpts) { + opts.config = config + } +} + func WithTLSConfig(tls *tls.Config) ServerOpt { return func(opts *ServerOpts) { opts.tls = tls @@ -115,6 +126,10 @@ func NewServer(fs ...ServerOpt) (*Server, error) { f(opts) } + if opts.config == nil { + return nil, fmt.Errorf("config is required. use WithConfig") + } + if opts.tls == nil { return nil, fmt.Errorf("tls config is required. use WithTLSConfig") } @@ -124,6 +139,7 @@ func NewServer(fs ...ServerOpt) (*Server, error) { return &Server{ l: opts.l, + config: opts.config, port: opts.port, bindAddress: opts.bindAddress, ingestor: opts.ingestor, @@ -155,6 +171,16 @@ func (s *Server) startGRPC(ctx context.Context) error { serverOpts = append(serverOpts, grpc.Creds(credentials.NewTLS(s.tls))) } + authMiddleware := middleware.NewAuthN(s.config) + + serverOpts = append(serverOpts, grpc.StreamInterceptor( + auth.StreamServerInterceptor(authMiddleware.Middleware), + )) + + serverOpts = append(serverOpts, grpc.UnaryInterceptor( + auth.UnaryServerInterceptor(authMiddleware.Middleware), + )) + grpcServer := grpc.NewServer(serverOpts...) if s.ingestor != nil { diff --git a/internal/services/ingestor/contracts/events.pb.go b/internal/services/ingestor/contracts/events.pb.go index b9a360aae..93b3c91a7 100644 --- a/internal/services/ingestor/contracts/events.pb.go +++ b/internal/services/ingestor/contracts/events.pb.go @@ -110,14 +110,12 @@ type PushEventRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // the tenant id - TenantId string `protobuf:"bytes,1,opt,name=tenantId,proto3" json:"tenantId,omitempty"` // the key for the event - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // the payload for the event - Payload string `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` + Payload string `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` // when the event was generated - EventTimestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=eventTimestamp,proto3" json:"eventTimestamp,omitempty"` + EventTimestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=eventTimestamp,proto3" json:"eventTimestamp,omitempty"` } func (x *PushEventRequest) Reset() { @@ -152,13 +150,6 @@ func (*PushEventRequest) Descriptor() ([]byte, []int) { return file_events_proto_rawDescGZIP(), []int{1} } -func (x *PushEventRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *PushEventRequest) GetKey() string { if x != nil { return x.Key @@ -185,12 +176,10 @@ type ListEventRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // (required) the tenant id - TenantId string `protobuf:"bytes,1,opt,name=tenantId,proto3" json:"tenantId,omitempty"` // (optional) the number of events to skip - Offset int32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Offset int32 `protobuf:"varint,1,opt,name=offset,proto3" json:"offset,omitempty"` // (optional) the key for the event - Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` } func (x *ListEventRequest) Reset() { @@ -225,13 +214,6 @@ func (*ListEventRequest) Descriptor() ([]byte, []int) { return file_events_proto_rawDescGZIP(), []int{2} } -func (x *ListEventRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *ListEventRequest) GetOffset() int32 { if x != nil { return x.Offset @@ -299,10 +281,8 @@ type ReplayEventRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // the tenant id - TenantId string `protobuf:"bytes,1,opt,name=tenantId,proto3" json:"tenantId,omitempty"` // the event id to replay - EventId string `protobuf:"bytes,2,opt,name=eventId,proto3" json:"eventId,omitempty"` + EventId string `protobuf:"bytes,1,opt,name=eventId,proto3" json:"eventId,omitempty"` } func (x *ReplayEventRequest) Reset() { @@ -337,13 +317,6 @@ func (*ReplayEventRequest) Descriptor() ([]byte, []int) { return file_events_proto_rawDescGZIP(), []int{4} } -func (x *ReplayEventRequest) GetTenantId() string { - if x != nil { - return x.TenantId - } - return "" -} - func (x *ReplayEventRequest) GetEventId() string { if x != nil { return x.EventId @@ -368,45 +341,40 @@ var file_events_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, - 0x9e, 0x01, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, + 0x82, 0x01, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, + 0x12, 0x42, 0x0a, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3c, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x42, 0x0a, 0x0e, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x22, 0x58, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, - 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x33, 0x0a, 0x11, 0x4c, 0x69, - 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x1e, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, - 0x4a, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x32, 0x99, 0x01, 0x0a, 0x0d, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x23, 0x0a, - 0x04, 0x50, 0x75, 0x73, 0x68, 0x12, 0x11, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x53, 0x69, 0x6e, - 0x67, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, - 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x42, 0x47, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, - 0x76, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x64, 0x69, 0x73, 0x70, - 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x79, 0x22, 0x33, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x2e, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6c, 0x61, + 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, + 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x32, 0x99, 0x01, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x23, 0x0a, 0x04, 0x50, 0x75, 0x73, + 0x68, 0x12, 0x11, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x2f, + 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x32, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x22, 0x00, 0x42, 0x47, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x68, 0x61, 0x74, 0x63, 0x68, 0x65, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x61, + 0x74, 0x63, 0x68, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/services/ingestor/server.go b/internal/services/ingestor/server.go index 84de4419e..3673dfc17 100644 --- a/internal/services/ingestor/server.go +++ b/internal/services/ingestor/server.go @@ -16,6 +16,8 @@ import ( ) func (i *IngestorImpl) Push(ctx context.Context, req *contracts.PushEventRequest) (*contracts.Event, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + eventDataMap := map[string]interface{}{} err := json.Unmarshal([]byte(req.Payload), &eventDataMap) @@ -24,7 +26,7 @@ func (i *IngestorImpl) Push(ctx context.Context, req *contracts.PushEventRequest return nil, err } - event, err := i.IngestEvent(ctx, req.TenantId, req.Key, eventDataMap) + event, err := i.IngestEvent(ctx, tenant.ID, req.Key, eventDataMap) if err != nil { return nil, err @@ -40,6 +42,8 @@ func (i *IngestorImpl) Push(ctx context.Context, req *contracts.PushEventRequest } func (i *IngestorImpl) List(ctx context.Context, req *contracts.ListEventRequest) (*contracts.ListEventResponse, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + offset := int(req.Offset) var keys []string @@ -47,7 +51,7 @@ func (i *IngestorImpl) List(ctx context.Context, req *contracts.ListEventRequest keys = []string{req.Key} } - listResult, err := i.eventRepository.ListEvents(req.TenantId, &repository.ListEventOpts{ + listResult, err := i.eventRepository.ListEvents(tenant.ID, &repository.ListEventOpts{ Keys: keys, Offset: &offset, }) @@ -74,13 +78,15 @@ func (i *IngestorImpl) List(ctx context.Context, req *contracts.ListEventRequest } func (i *IngestorImpl) ReplaySingleEvent(ctx context.Context, req *contracts.ReplayEventRequest) (*contracts.Event, error) { + tenant := ctx.Value("tenant").(*db.TenantModel) + oldEvent, err := i.eventRepository.GetEventById(req.EventId) if err != nil { return nil, err } - newEvent, err := i.IngestReplayedEvent(ctx, req.TenantId, oldEvent) + newEvent, err := i.IngestReplayedEvent(ctx, tenant.ID, oldEvent) if err != nil { return nil, err diff --git a/pkg/client/admin.go b/pkg/client/admin.go index a418861ee..e5f3a27db 100644 --- a/pkg/client/admin.go +++ b/pkg/client/admin.go @@ -31,6 +31,8 @@ type adminClientImpl struct { l *zerolog.Logger v validator.Validator + + ctx *contextLoader } func newAdmin(conn *grpc.ClientConn, opts *sharedClientOpts) AdminClient { @@ -39,6 +41,7 @@ func newAdmin(conn *grpc.ClientConn, opts *sharedClientOpts) AdminClient { tenantId: opts.tenantId, l: opts.l, v: opts.v, + ctx: opts.ctxLoader, } } @@ -75,9 +78,8 @@ func (a *adminClientImpl) PutWorkflow(workflow *types.Workflow, fs ...PutOptFunc return fmt.Errorf("could not get put opts: %w", err) } - apiWorkflow, err := a.client.GetWorkflowByName(context.Background(), &admincontracts.GetWorkflowByNameRequest{ - TenantId: a.tenantId, - Name: req.Opts.Name, + apiWorkflow, err := a.client.GetWorkflowByName(a.ctx.newContext(context.Background()), &admincontracts.GetWorkflowByNameRequest{ + Name: req.Opts.Name, }) shouldPut := opts.autoVersion @@ -114,7 +116,7 @@ func (a *adminClientImpl) PutWorkflow(workflow *types.Workflow, fs ...PutOptFunc } if shouldPut { - _, err = a.client.PutWorkflow(context.Background(), req) + _, err = a.client.PutWorkflow(a.ctx.newContext(context.Background()), req) if err != nil { return fmt.Errorf("could not create workflow: %w", err) @@ -159,9 +161,8 @@ func (a *adminClientImpl) ScheduleWorkflow(workflowName string, fs ...ScheduleOp } // get the workflow id from the name - workflow, err := a.client.GetWorkflowByName(context.Background(), &admincontracts.GetWorkflowByNameRequest{ - TenantId: a.tenantId, - Name: workflowName, + workflow, err := a.client.GetWorkflowByName(a.ctx.newContext(context.Background()), &admincontracts.GetWorkflowByNameRequest{ + Name: workflowName, }) if err != nil { @@ -180,8 +181,7 @@ func (a *adminClientImpl) ScheduleWorkflow(workflowName string, fs ...ScheduleOp return err } - _, err = a.client.ScheduleWorkflow(context.Background(), &admincontracts.ScheduleWorkflowRequest{ - TenantId: a.tenantId, + _, err = a.client.ScheduleWorkflow(a.ctx.newContext(context.Background()), &admincontracts.ScheduleWorkflowRequest{ WorkflowId: workflow.Id, Schedules: pbSchedules, Input: string(inputBytes), @@ -246,8 +246,7 @@ func (a *adminClientImpl) getPutRequest(workflow *types.Workflow) (*admincontrac opts.Jobs = jobOpts return &admincontracts.PutWorkflowRequest{ - TenantId: a.tenantId, - Opts: opts, + Opts: opts, }, nil } diff --git a/pkg/client/client.go b/pkg/client/client.go index 9b4286034..ac3a3acfc 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -45,6 +45,7 @@ type ClientOpts struct { v validator.Validator tls *tls.Config hostPort string + token string filesLoader filesLoaderFunc initWorkflows bool @@ -64,6 +65,7 @@ func defaultClientOpts() *ClientOpts { return &ClientOpts{ tenantId: clientConfig.TenantId, + token: clientConfig.Token, l: &logger, v: validator.NewDefaultValidator(), tls: clientConfig.TLSConfig, @@ -101,9 +103,10 @@ func WithWorkflows(files []*types.Workflow) ClientOpt { } type sharedClientOpts struct { - tenantId string - l *zerolog.Logger - v validator.Validator + tenantId string + l *zerolog.Logger + v validator.Validator + ctxLoader *contextLoader } // New creates a new client instance. @@ -119,6 +122,10 @@ func New(fs ...ClientOpt) (Client, error) { return nil, fmt.Errorf("tls config is required") } + if opts.token == "" { + return nil, fmt.Errorf("token is required") + } + opts.l.Debug().Msgf("connecting to %s with TLS server name %s", opts.hostPort, opts.tls.ServerName) conn, err := grpc.Dial( @@ -131,9 +138,10 @@ func New(fs ...ClientOpt) (Client, error) { } shared := &sharedClientOpts{ - tenantId: opts.tenantId, - l: opts.l, - v: opts.v, + tenantId: opts.tenantId, + l: opts.l, + v: opts.v, + ctxLoader: newContextLoader(opts.token), } admin := newAdmin(conn, shared) diff --git a/pkg/client/context.go b/pkg/client/context.go new file mode 100644 index 000000000..58000578d --- /dev/null +++ b/pkg/client/context.go @@ -0,0 +1,26 @@ +package client + +import ( + grpcMetadata "google.golang.org/grpc/metadata" + + "context" +) + +type contextLoader struct { + // The token + Token string +} + +func newContextLoader(token string) *contextLoader { + return &contextLoader{ + Token: token, + } +} + +func (c *contextLoader) newContext(ctx context.Context) context.Context { + md := grpcMetadata.New(map[string]string{ + "authorization": "Bearer " + c.Token, + }) + + return grpcMetadata.NewOutgoingContext(ctx, md) +} diff --git a/pkg/client/dispatcher.go b/pkg/client/dispatcher.go index 5c9668ed2..e6e5b226d 100644 --- a/pkg/client/dispatcher.go +++ b/pkg/client/dispatcher.go @@ -123,6 +123,8 @@ type dispatcherClientImpl struct { l *zerolog.Logger v validator.Validator + + ctx *contextLoader } func newDispatcher(conn *grpc.ClientConn, opts *sharedClientOpts) DispatcherClient { @@ -131,6 +133,7 @@ func newDispatcher(conn *grpc.ClientConn, opts *sharedClientOpts) DispatcherClie tenantId: opts.tenantId, l: opts.l, v: opts.v, + ctx: opts.ctxLoader, } } @@ -146,6 +149,8 @@ type actionListenerImpl struct { l *zerolog.Logger v validator.Validator + + ctx *contextLoader } func (d *dispatcherClientImpl) newActionListener(ctx context.Context, req *GetActionListenerRequest) (*actionListenerImpl, error) { @@ -155,8 +160,7 @@ func (d *dispatcherClientImpl) newActionListener(ctx context.Context, req *GetAc } // register the worker - resp, err := d.client.Register(ctx, &dispatchercontracts.WorkerRegisterRequest{ - TenantId: d.tenantId, + resp, err := d.client.Register(d.ctx.newContext(ctx), &dispatchercontracts.WorkerRegisterRequest{ WorkerName: req.WorkerName, Actions: req.Actions, Services: req.Services, @@ -169,8 +173,7 @@ func (d *dispatcherClientImpl) newActionListener(ctx context.Context, req *GetAc d.l.Debug().Msgf("Registered worker with id: %s", resp.WorkerId) // subscribe to the worker - listener, err := d.client.Listen(ctx, &dispatchercontracts.WorkerListenRequest{ - TenantId: d.tenantId, + listener, err := d.client.Listen(d.ctx.newContext(ctx), &dispatchercontracts.WorkerListenRequest{ WorkerId: resp.WorkerId, }) @@ -185,6 +188,7 @@ func (d *dispatcherClientImpl) newActionListener(ctx context.Context, req *GetAc l: d.l, v: d.v, tenantId: d.tenantId, + ctx: d.ctx, }, nil } @@ -279,8 +283,7 @@ func (a *actionListenerImpl) retrySubscribe(ctx context.Context) error { for retries < DefaultActionListenerRetryCount { time.Sleep(DefaultActionListenerRetryInterval) - listenClient, err := a.client.Listen(ctx, &dispatchercontracts.WorkerListenRequest{ - TenantId: a.tenantId, + listenClient, err := a.client.Listen(a.ctx.newContext(ctx), &dispatchercontracts.WorkerListenRequest{ WorkerId: a.workerId, }) @@ -300,9 +303,8 @@ func (a *actionListenerImpl) retrySubscribe(ctx context.Context) error { func (a *actionListenerImpl) Unregister() error { _, err := a.client.Unsubscribe( - context.Background(), + a.ctx.newContext(context.Background()), &dispatchercontracts.WorkerUnsubscribeRequest{ - TenantId: a.tenantId, WorkerId: a.workerId, }, ) @@ -343,8 +345,7 @@ func (d *dispatcherClientImpl) SendActionEvent(ctx context.Context, in *ActionEv actionEventType = dispatchercontracts.ActionEventType_STEP_EVENT_TYPE_UNKNOWN } - resp, err := d.client.SendActionEvent(ctx, &dispatchercontracts.ActionEvent{ - TenantId: d.tenantId, + resp, err := d.client.SendActionEvent(d.ctx.newContext(ctx), &dispatchercontracts.ActionEvent{ WorkerId: in.WorkerId, JobId: in.JobId, JobRunId: in.JobRunId, diff --git a/pkg/client/event.go b/pkg/client/event.go index d476c0ddb..d72a4d321 100644 --- a/pkg/client/event.go +++ b/pkg/client/event.go @@ -24,6 +24,8 @@ type eventClientImpl struct { l *zerolog.Logger v validator.Validator + + ctx *contextLoader } func newEvent(conn *grpc.ClientConn, opts *sharedClientOpts) EventClient { @@ -32,6 +34,7 @@ func newEvent(conn *grpc.ClientConn, opts *sharedClientOpts) EventClient { tenantId: opts.tenantId, l: opts.l, v: opts.v, + ctx: opts.ctxLoader, } } @@ -42,8 +45,7 @@ func (a *eventClientImpl) Push(ctx context.Context, eventKey string, payload int return err } - _, err = a.client.Push(ctx, &eventcontracts.PushEventRequest{ - TenantId: a.tenantId, + _, err = a.client.Push(a.ctx.newContext(ctx), &eventcontracts.PushEventRequest{ Key: eventKey, Payload: string(payloadBytes), EventTimestamp: timestamppb.Now(), diff --git a/pkg/client/loader/loader.go b/pkg/client/loader/loader.go index 0b0dfed8f..8e0e32217 100644 --- a/pkg/client/loader/loader.go +++ b/pkg/client/loader/loader.go @@ -2,7 +2,9 @@ package loader import ( "fmt" + "net/url" "path/filepath" + "strings" "github.com/hatchet-dev/hatchet/internal/config/client" "github.com/hatchet-dev/hatchet/internal/config/loader/loaderutils" @@ -41,7 +43,21 @@ func LoadClientConfigFile(files ...[]byte) (*client.ClientConfigFile, error) { } func GetClientConfigFromConfigFile(cf *client.ClientConfigFile) (res *client.ClientConfig, err error) { - tlsConf, err := loaderutils.LoadClientTLSConfig(&cf.TLS) + tlsServerName := cf.TLS.TLSServerName + + // if the tls server name is empty, parse the domain from the host:port + if tlsServerName == "" { + // parse the domain from the host:port + domain, err := parseDomain(cf.HostPort) + + if err != nil { + return nil, fmt.Errorf("could not parse domain: %w", err) + } + + tlsServerName = domain.Hostname() + } + + tlsConf, err := loaderutils.LoadClientTLSConfig(&cf.TLS, tlsServerName) if err != nil { return nil, fmt.Errorf("could not load TLS config: %w", err) @@ -50,5 +66,13 @@ func GetClientConfigFromConfigFile(cf *client.ClientConfigFile) (res *client.Cli return &client.ClientConfig{ TenantId: cf.TenantId, TLSConfig: tlsConf, + Token: cf.Token, }, nil } + +func parseDomain(domain string) (*url.URL, error) { + if !strings.HasPrefix(domain, "http://") && !strings.HasPrefix(domain, "https://") { + domain = "https://" + domain + } + return url.Parse(domain) +} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ce9fd2850..eb372e2a7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -53,10 +53,10 @@ model UserOAuth { providerUserId String // the oauth provider's access token - accessToken Bytes + accessToken Bytes @db.ByteA // the oauth provider's refresh token - refreshToken Bytes? + refreshToken Bytes? @db.ByteA // the oauth provider's expiry time expiresAt DateTime? @@ -182,6 +182,9 @@ model APIToken { // whether the token has been revoked revoked Boolean @default(false) + // an optional name for the token + name String? + tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade, onUpdate: Cascade) tenantId String? @db.Uuid } @@ -402,7 +405,9 @@ model Job { model Action { // base fields - id String @id + id String @id @unique @default(uuid()) @db.Uuid + + actionId String // the action description description String? @@ -417,7 +422,7 @@ model Action { workers Worker[] // actions are unique per tenant - @@unique([tenantId, id]) + @@unique([tenantId, actionId]) } model Step { @@ -439,7 +444,7 @@ model Step { jobId String @db.Uuid // an action id for the step - action Action @relation(fields: [actionId, tenantId], references: [id, tenantId]) + action Action @relation(fields: [actionId, tenantId], references: [actionId, tenantId]) actionId String timeout String? diff --git a/python-client/generate.sh b/python-client/generate.sh index 98d0334a7..f31eeaa1f 100644 --- a/python-client/generate.sh +++ b/python-client/generate.sh @@ -2,6 +2,6 @@ # # Builds python auto-generated protobuf files -poetry run python -m grpc_tools.protoc --proto_path=../api-contracts/dispatcher --python_out=./hatchet --pyi_out=./hatchet --grpc_python_out=./hatchet dispatcher.proto -poetry run python -m grpc_tools.protoc --proto_path=../api-contracts/events --python_out=./hatchet --pyi_out=./hatchet --grpc_python_out=./hatchet events.proto -poetry run python -m grpc_tools.protoc --proto_path=../api-contracts/workflows --python_out=./hatchet --pyi_out=./hatchet --grpc_python_out=./hatchet workflows.proto +poetry run python -m grpc_tools.protoc --proto_path=../api-contracts/dispatcher --python_out=./hatchet_sdk --pyi_out=./hatchet_sdk --grpc_python_out=./hatchet_sdk dispatcher.proto +poetry run python -m grpc_tools.protoc --proto_path=../api-contracts/events --python_out=./hatchet_sdk --pyi_out=./hatchet_sdk --grpc_python_out=./hatchet_sdk events.proto +poetry run python -m grpc_tools.protoc --proto_path=../api-contracts/workflows --python_out=./hatchet_sdk --pyi_out=./hatchet_sdk --grpc_python_out=./hatchet_sdk workflows.proto diff --git a/python-client/hatchet_sdk/client.py b/python-client/hatchet_sdk/client.py index 9b17f1d75..c719fab88 100644 --- a/python-client/hatchet_sdk/client.py +++ b/python-client/hatchet_sdk/client.py @@ -1,4 +1,5 @@ # relative imports +from typing import Any from .clients.admin import AdminClientImpl, new_admin from .clients.events import EventClientImpl, new_event from .clients.dispatcher import DispatcherClientImpl, new_dispatcher @@ -53,14 +54,27 @@ def new_client(*opts_functions): if config.host_port is None: raise ValueError("Host and port are required") + + credentials : grpc.ChannelCredentials | None = None + + # load channel credentials + if config.tls_config.tls_strategy == 'tls': + root : Any | None = None + + if config.tls_config.ca_file: + root = open(config.tls_config.ca_file, "rb").read() - root = open(config.tls_config.ca_file, "rb").read() - private_key = open(config.tls_config.key_file, "rb").read() - certificate_chain = open(config.tls_config.cert_file, "rb").read() + credentials = grpc.ssl_channel_credentials(root_certificates=root) + elif config.tls_config.tls_strategy == 'mtls': + root = open(config.tls_config.ca_file, "rb").read() + private_key = open(config.tls_config.key_file, "rb").read() + certificate_chain = open(config.tls_config.cert_file, "rb").read() + credentials = grpc.ssl_channel_credentials(root_certificates=root, private_key=private_key, certificate_chain=certificate_chain) + conn = grpc.secure_channel( target=config.host_port, - credentials=grpc.ssl_channel_credentials(root_certificates=root, private_key=private_key, certificate_chain=certificate_chain), + credentials=credentials, options=[('grpc.ssl_target_name_override', config.tls_config.server_name)], ) diff --git a/python-client/hatchet_sdk/clients/admin.py b/python-client/hatchet_sdk/clients/admin.py index 447ddb072..e918c9f89 100644 --- a/python-client/hatchet_sdk/clients/admin.py +++ b/python-client/hatchet_sdk/clients/admin.py @@ -5,21 +5,19 @@ from ..workflows_pb2 import CreateWorkflowVersionOpts, ScheduleWorkflowRequest, PutWorkflowRequest, GetWorkflowByNameRequest, Workflow from ..loader import ClientConfig from ..semver import bump_minor_version +from ..metadata import get_metadata + def new_admin(conn, config: ClientConfig): return AdminClientImpl( client=WorkflowServiceStub(conn), - tenant_id=config.tenant_id, - # logger=shared_opts['logger'], - # validator=shared_opts['validator'], + token=config.token, ) class AdminClientImpl: - def __init__(self, client : WorkflowServiceStub, tenant_id): + def __init__(self, client : WorkflowServiceStub, token): self.client = client - self.tenant_id = tenant_id - # self.logger = logger - # self.validator = validator + self.token = token def put_workflow(self, workflow: CreateWorkflowVersionOpts, auto_version: bool = False): if workflow.version == "" and not auto_version: @@ -31,9 +29,9 @@ def put_workflow(self, workflow: CreateWorkflowVersionOpts, auto_version: bool = try: existing_workflow : Workflow = self.client.GetWorkflowByName( GetWorkflowByNameRequest( - tenant_id=self.tenant_id, name=workflow.name, - ) + ), + metadata=get_metadata(self.token), ) except grpc.RpcError as e: if e.code() == grpc.StatusCode.NOT_FOUND: @@ -63,9 +61,9 @@ def put_workflow(self, workflow: CreateWorkflowVersionOpts, auto_version: bool = try: self.client.PutWorkflow( PutWorkflowRequest( - tenant_id=self.tenant_id, opts=workflow, - ) + ), + metadata=get_metadata(self.token), ) except grpc.RpcError as e: raise ValueError(f"Could not create/update workflow: {e}") @@ -76,6 +74,6 @@ def schedule_workflow(self, workflow_id : str, schedules : List[timestamp_pb2.Ti tenant_id=self.tenant_id, workflow_id=workflow_id, schedules=schedules, - )) + ), metadata=get_metadata(self.token)) except grpc.RpcError as e: raise ValueError(f"gRPC error: {e}") diff --git a/python-client/hatchet_sdk/clients/dispatcher.py b/python-client/hatchet_sdk/clients/dispatcher.py index 7657f3c44..d042e638d 100644 --- a/python-client/hatchet_sdk/clients/dispatcher.py +++ b/python-client/hatchet_sdk/clients/dispatcher.py @@ -8,13 +8,13 @@ import json import grpc from typing import Callable, List, Union +from ..metadata import get_metadata + def new_dispatcher(conn, config: ClientConfig): return DispatcherClientImpl( client=DispatcherStub(conn), - tenant_id=config.tenant_id, - # logger=shared_opts['logger'], - # validator=shared_opts['validator'], + token=config.token, ) class DispatcherClient: @@ -60,9 +60,9 @@ def unregister(self): CANCEL_STEP_RUN = 1 class ActionListenerImpl(WorkerActionListener): - def __init__(self, client : DispatcherStub, tenant_id, worker_id): + def __init__(self, client : DispatcherStub, token, worker_id): self.client = client - self.tenant_id = tenant_id + self.token = token self.worker_id = worker_id self.retries = 0 @@ -145,42 +145,43 @@ def get_listen_client(self): time.sleep(DEFAULT_ACTION_LISTENER_RETRY_INTERVAL) return self.client.Listen(WorkerListenRequest( - tenantId=self.tenant_id, workerId=self.worker_id - ), timeout=DEFAULT_ACTION_TIMEOUT) + ), + timeout=DEFAULT_ACTION_TIMEOUT, + metadata=get_metadata(self.token), + ) def unregister(self): try: self.client.Unsubscribe( WorkerUnsubscribeRequest( - tenantId=self.tenant_id, workerId=self.worker_id ), timeout=DEFAULT_REGISTER_TIMEOUT, + metadata=get_metadata(self.token), ) except grpc.RpcError as e: raise Exception(f"Failed to unsubscribe: {e}") class DispatcherClientImpl(DispatcherClient): - def __init__(self, client : DispatcherStub, tenant_id): + def __init__(self, client : DispatcherStub, token): self.client = client - self.tenant_id = tenant_id + self.token = token # self.logger = logger # self.validator = validator def get_action_listener(self, req: GetActionListenerRequest) -> ActionListenerImpl: # Register the worker response : WorkerRegisterResponse = self.client.Register(WorkerRegisterRequest( - tenantId=self.tenant_id, workerName=req.worker_name, actions=req.actions, services=req.services - ), timeout=DEFAULT_REGISTER_TIMEOUT) + ), timeout=DEFAULT_REGISTER_TIMEOUT, metadata=get_metadata(self.token)) - return ActionListenerImpl(self.client, self.tenant_id, response.workerId) + return ActionListenerImpl(self.client, self.token, response.workerId) def send_action_event(self, in_: ActionEvent): - response : ActionEventResponse = self.client.SendActionEvent(in_) + response : ActionEventResponse = self.client.SendActionEvent(in_, metadata=get_metadata(self.token),) return response diff --git a/python-client/hatchet_sdk/clients/events.py b/python-client/hatchet_sdk/clients/events.py index 30139986d..8297129c6 100644 --- a/python-client/hatchet_sdk/clients/events.py +++ b/python-client/hatchet_sdk/clients/events.py @@ -6,21 +6,18 @@ import json import grpc from google.protobuf import timestamp_pb2 +from ..metadata import get_metadata def new_event(conn, config: ClientConfig): return EventClientImpl( client=EventsServiceStub(conn), - tenant_id=config.tenant_id, - # logger=shared_opts['logger'], - # validator=shared_opts['validator'], + token=config.token, ) class EventClientImpl: - def __init__(self, client, tenant_id): + def __init__(self, client, token): self.client = client - self.tenant_id = tenant_id - # self.logger = logger - # self.validator = validator + self.token = token def push(self, event_key, payload): try: @@ -29,13 +26,12 @@ def push(self, event_key, payload): raise ValueError(f"Error encoding payload: {e}") request = PushEventRequest( - tenantId=self.tenant_id, key=event_key, payload=payload_bytes, eventTimestamp=timestamp_pb2.Timestamp().FromDatetime(datetime.datetime.now()), ) try: - self.client.Push(request) + self.client.Push(request, metadata=get_metadata(self.token)) except grpc.RpcError as e: raise ValueError(f"gRPC error: {e}") diff --git a/python-client/hatchet_sdk/dispatcher_pb2.py b/python-client/hatchet_sdk/dispatcher_pb2.py index fb602b3ad..826e0d7e2 100644 --- a/python-client/hatchet_sdk/dispatcher_pb2.py +++ b/python-client/hatchet_sdk/dispatcher_pb2.py @@ -15,7 +15,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x64ispatcher.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"`\n\x15WorkerRegisterRequest\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x12\n\nworkerName\x18\x02 \x01(\t\x12\x0f\n\x07\x61\x63tions\x18\x03 \x03(\t\x12\x10\n\x08services\x18\x04 \x03(\t\"P\n\x16WorkerRegisterResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\x12\x12\n\nworkerName\x18\x03 \x01(\t\"\xc1\x01\n\x0e\x41ssignedAction\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\r\n\x05jobId\x18\x02 \x01(\t\x12\x0f\n\x07jobName\x18\x03 \x01(\t\x12\x10\n\x08jobRunId\x18\x04 \x01(\t\x12\x0e\n\x06stepId\x18\x05 \x01(\t\x12\x11\n\tstepRunId\x18\x06 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\x07 \x01(\t\x12\x1f\n\nactionType\x18\x08 \x01(\x0e\x32\x0b.ActionType\x12\x15\n\ractionPayload\x18\t \x01(\t\"9\n\x13WorkerListenRequest\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\">\n\x18WorkerUnsubscribeRequest\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\"?\n\x19WorkerUnsubscribeResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\"\xf6\x01\n\x0b\x41\x63tionEvent\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\x12\r\n\x05jobId\x18\x03 \x01(\t\x12\x10\n\x08jobRunId\x18\x04 \x01(\t\x12\x0e\n\x06stepId\x18\x05 \x01(\t\x12\x11\n\tstepRunId\x18\x06 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\x07 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12#\n\teventType\x18\t \x01(\x0e\x32\x10.ActionEventType\x12\x14\n\x0c\x65ventPayload\x18\n \x01(\t\"9\n\x13\x41\x63tionEventResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t*5\n\nActionType\x12\x12\n\x0eSTART_STEP_RUN\x10\x00\x12\x13\n\x0f\x43\x41NCEL_STEP_RUN\x10\x01*\x86\x01\n\x0f\x41\x63tionEventType\x12\x1b\n\x17STEP_EVENT_TYPE_UNKNOWN\x10\x00\x12\x1b\n\x17STEP_EVENT_TYPE_STARTED\x10\x01\x12\x1d\n\x19STEP_EVENT_TYPE_COMPLETED\x10\x02\x12\x1a\n\x16STEP_EVENT_TYPE_FAILED\x10\x03\x32\x81\x02\n\nDispatcher\x12=\n\x08Register\x12\x16.WorkerRegisterRequest\x1a\x17.WorkerRegisterResponse\"\x00\x12\x33\n\x06Listen\x12\x14.WorkerListenRequest\x1a\x0f.AssignedAction\"\x00\x30\x01\x12\x37\n\x0fSendActionEvent\x12\x0c.ActionEvent\x1a\x14.ActionEventResponse\"\x00\x12\x46\n\x0bUnsubscribe\x12\x19.WorkerUnsubscribeRequest\x1a\x1a.WorkerUnsubscribeResponse\"\x00\x42GZEgithub.com/hatchet-dev/hatchet/internal/services/dispatcher/contractsb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x64ispatcher.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"N\n\x15WorkerRegisterRequest\x12\x12\n\nworkerName\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x63tions\x18\x02 \x03(\t\x12\x10\n\x08services\x18\x03 \x03(\t\"P\n\x16WorkerRegisterResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\x12\x12\n\nworkerName\x18\x03 \x01(\t\"\xc1\x01\n\x0e\x41ssignedAction\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\r\n\x05jobId\x18\x02 \x01(\t\x12\x0f\n\x07jobName\x18\x03 \x01(\t\x12\x10\n\x08jobRunId\x18\x04 \x01(\t\x12\x0e\n\x06stepId\x18\x05 \x01(\t\x12\x11\n\tstepRunId\x18\x06 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\x07 \x01(\t\x12\x1f\n\nactionType\x18\x08 \x01(\x0e\x32\x0b.ActionType\x12\x15\n\ractionPayload\x18\t \x01(\t\"\'\n\x13WorkerListenRequest\x12\x10\n\x08workerId\x18\x01 \x01(\t\",\n\x18WorkerUnsubscribeRequest\x12\x10\n\x08workerId\x18\x01 \x01(\t\"?\n\x19WorkerUnsubscribeResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t\"\xe4\x01\n\x0b\x41\x63tionEvent\x12\x10\n\x08workerId\x18\x01 \x01(\t\x12\r\n\x05jobId\x18\x02 \x01(\t\x12\x10\n\x08jobRunId\x18\x03 \x01(\t\x12\x0e\n\x06stepId\x18\x04 \x01(\t\x12\x11\n\tstepRunId\x18\x05 \x01(\t\x12\x10\n\x08\x61\x63tionId\x18\x06 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12#\n\teventType\x18\x08 \x01(\x0e\x32\x10.ActionEventType\x12\x14\n\x0c\x65ventPayload\x18\t \x01(\t\"9\n\x13\x41\x63tionEventResponse\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x10\n\x08workerId\x18\x02 \x01(\t*5\n\nActionType\x12\x12\n\x0eSTART_STEP_RUN\x10\x00\x12\x13\n\x0f\x43\x41NCEL_STEP_RUN\x10\x01*\x86\x01\n\x0f\x41\x63tionEventType\x12\x1b\n\x17STEP_EVENT_TYPE_UNKNOWN\x10\x00\x12\x1b\n\x17STEP_EVENT_TYPE_STARTED\x10\x01\x12\x1d\n\x19STEP_EVENT_TYPE_COMPLETED\x10\x02\x12\x1a\n\x16STEP_EVENT_TYPE_FAILED\x10\x03\x32\x81\x02\n\nDispatcher\x12=\n\x08Register\x12\x16.WorkerRegisterRequest\x1a\x17.WorkerRegisterResponse\"\x00\x12\x33\n\x06Listen\x12\x14.WorkerListenRequest\x1a\x0f.AssignedAction\"\x00\x30\x01\x12\x37\n\x0fSendActionEvent\x12\x0c.ActionEvent\x1a\x14.ActionEventResponse\"\x00\x12\x46\n\x0bUnsubscribe\x12\x19.WorkerUnsubscribeRequest\x1a\x1a.WorkerUnsubscribeResponse\"\x00\x42GZEgithub.com/hatchet-dev/hatchet/internal/services/dispatcher/contractsb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -23,26 +23,26 @@ if _descriptor._USE_C_DESCRIPTORS == False: _globals['DESCRIPTOR']._options = None _globals['DESCRIPTOR']._serialized_options = b'ZEgithub.com/hatchet-dev/hatchet/internal/services/dispatcher/contracts' - _globals['_ACTIONTYPE']._serialized_start=925 - _globals['_ACTIONTYPE']._serialized_end=978 - _globals['_ACTIONEVENTTYPE']._serialized_start=981 - _globals['_ACTIONEVENTTYPE']._serialized_end=1115 + _globals['_ACTIONTYPE']._serialized_start=853 + _globals['_ACTIONTYPE']._serialized_end=906 + _globals['_ACTIONEVENTTYPE']._serialized_start=909 + _globals['_ACTIONEVENTTYPE']._serialized_end=1043 _globals['_WORKERREGISTERREQUEST']._serialized_start=53 - _globals['_WORKERREGISTERREQUEST']._serialized_end=149 - _globals['_WORKERREGISTERRESPONSE']._serialized_start=151 - _globals['_WORKERREGISTERRESPONSE']._serialized_end=231 - _globals['_ASSIGNEDACTION']._serialized_start=234 - _globals['_ASSIGNEDACTION']._serialized_end=427 - _globals['_WORKERLISTENREQUEST']._serialized_start=429 - _globals['_WORKERLISTENREQUEST']._serialized_end=486 - _globals['_WORKERUNSUBSCRIBEREQUEST']._serialized_start=488 - _globals['_WORKERUNSUBSCRIBEREQUEST']._serialized_end=550 - _globals['_WORKERUNSUBSCRIBERESPONSE']._serialized_start=552 - _globals['_WORKERUNSUBSCRIBERESPONSE']._serialized_end=615 - _globals['_ACTIONEVENT']._serialized_start=618 - _globals['_ACTIONEVENT']._serialized_end=864 - _globals['_ACTIONEVENTRESPONSE']._serialized_start=866 - _globals['_ACTIONEVENTRESPONSE']._serialized_end=923 - _globals['_DISPATCHER']._serialized_start=1118 - _globals['_DISPATCHER']._serialized_end=1375 + _globals['_WORKERREGISTERREQUEST']._serialized_end=131 + _globals['_WORKERREGISTERRESPONSE']._serialized_start=133 + _globals['_WORKERREGISTERRESPONSE']._serialized_end=213 + _globals['_ASSIGNEDACTION']._serialized_start=216 + _globals['_ASSIGNEDACTION']._serialized_end=409 + _globals['_WORKERLISTENREQUEST']._serialized_start=411 + _globals['_WORKERLISTENREQUEST']._serialized_end=450 + _globals['_WORKERUNSUBSCRIBEREQUEST']._serialized_start=452 + _globals['_WORKERUNSUBSCRIBEREQUEST']._serialized_end=496 + _globals['_WORKERUNSUBSCRIBERESPONSE']._serialized_start=498 + _globals['_WORKERUNSUBSCRIBERESPONSE']._serialized_end=561 + _globals['_ACTIONEVENT']._serialized_start=564 + _globals['_ACTIONEVENT']._serialized_end=792 + _globals['_ACTIONEVENTRESPONSE']._serialized_start=794 + _globals['_ACTIONEVENTRESPONSE']._serialized_end=851 + _globals['_DISPATCHER']._serialized_start=1046 + _globals['_DISPATCHER']._serialized_end=1303 # @@protoc_insertion_point(module_scope) diff --git a/python-client/hatchet_sdk/dispatcher_pb2.pyi b/python-client/hatchet_sdk/dispatcher_pb2.pyi index a3fcbeca6..05299d4ae 100644 --- a/python-client/hatchet_sdk/dispatcher_pb2.pyi +++ b/python-client/hatchet_sdk/dispatcher_pb2.pyi @@ -26,16 +26,14 @@ STEP_EVENT_TYPE_COMPLETED: ActionEventType STEP_EVENT_TYPE_FAILED: ActionEventType class WorkerRegisterRequest(_message.Message): - __slots__ = ("tenantId", "workerName", "actions", "services") - TENANTID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("workerName", "actions", "services") WORKERNAME_FIELD_NUMBER: _ClassVar[int] ACTIONS_FIELD_NUMBER: _ClassVar[int] SERVICES_FIELD_NUMBER: _ClassVar[int] - tenantId: str workerName: str actions: _containers.RepeatedScalarFieldContainer[str] services: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, tenantId: _Optional[str] = ..., workerName: _Optional[str] = ..., actions: _Optional[_Iterable[str]] = ..., services: _Optional[_Iterable[str]] = ...) -> None: ... + def __init__(self, workerName: _Optional[str] = ..., actions: _Optional[_Iterable[str]] = ..., services: _Optional[_Iterable[str]] = ...) -> None: ... class WorkerRegisterResponse(_message.Message): __slots__ = ("tenantId", "workerId", "workerName") @@ -70,20 +68,16 @@ class AssignedAction(_message.Message): def __init__(self, tenantId: _Optional[str] = ..., jobId: _Optional[str] = ..., jobName: _Optional[str] = ..., jobRunId: _Optional[str] = ..., stepId: _Optional[str] = ..., stepRunId: _Optional[str] = ..., actionId: _Optional[str] = ..., actionType: _Optional[_Union[ActionType, str]] = ..., actionPayload: _Optional[str] = ...) -> None: ... class WorkerListenRequest(_message.Message): - __slots__ = ("tenantId", "workerId") - TENANTID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("workerId",) WORKERID_FIELD_NUMBER: _ClassVar[int] - tenantId: str workerId: str - def __init__(self, tenantId: _Optional[str] = ..., workerId: _Optional[str] = ...) -> None: ... + def __init__(self, workerId: _Optional[str] = ...) -> None: ... class WorkerUnsubscribeRequest(_message.Message): - __slots__ = ("tenantId", "workerId") - TENANTID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("workerId",) WORKERID_FIELD_NUMBER: _ClassVar[int] - tenantId: str workerId: str - def __init__(self, tenantId: _Optional[str] = ..., workerId: _Optional[str] = ...) -> None: ... + def __init__(self, workerId: _Optional[str] = ...) -> None: ... class WorkerUnsubscribeResponse(_message.Message): __slots__ = ("tenantId", "workerId") @@ -94,8 +88,7 @@ class WorkerUnsubscribeResponse(_message.Message): def __init__(self, tenantId: _Optional[str] = ..., workerId: _Optional[str] = ...) -> None: ... class ActionEvent(_message.Message): - __slots__ = ("tenantId", "workerId", "jobId", "jobRunId", "stepId", "stepRunId", "actionId", "eventTimestamp", "eventType", "eventPayload") - TENANTID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("workerId", "jobId", "jobRunId", "stepId", "stepRunId", "actionId", "eventTimestamp", "eventType", "eventPayload") WORKERID_FIELD_NUMBER: _ClassVar[int] JOBID_FIELD_NUMBER: _ClassVar[int] JOBRUNID_FIELD_NUMBER: _ClassVar[int] @@ -105,7 +98,6 @@ class ActionEvent(_message.Message): EVENTTIMESTAMP_FIELD_NUMBER: _ClassVar[int] EVENTTYPE_FIELD_NUMBER: _ClassVar[int] EVENTPAYLOAD_FIELD_NUMBER: _ClassVar[int] - tenantId: str workerId: str jobId: str jobRunId: str @@ -115,7 +107,7 @@ class ActionEvent(_message.Message): eventTimestamp: _timestamp_pb2.Timestamp eventType: ActionEventType eventPayload: str - def __init__(self, tenantId: _Optional[str] = ..., workerId: _Optional[str] = ..., jobId: _Optional[str] = ..., jobRunId: _Optional[str] = ..., stepId: _Optional[str] = ..., stepRunId: _Optional[str] = ..., actionId: _Optional[str] = ..., eventTimestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., eventType: _Optional[_Union[ActionEventType, str]] = ..., eventPayload: _Optional[str] = ...) -> None: ... + def __init__(self, workerId: _Optional[str] = ..., jobId: _Optional[str] = ..., jobRunId: _Optional[str] = ..., stepId: _Optional[str] = ..., stepRunId: _Optional[str] = ..., actionId: _Optional[str] = ..., eventTimestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., eventType: _Optional[_Union[ActionEventType, str]] = ..., eventPayload: _Optional[str] = ...) -> None: ... class ActionEventResponse(_message.Message): __slots__ = ("tenantId", "workerId") diff --git a/python-client/hatchet_sdk/dispatcher_pb2_grpc.py b/python-client/hatchet_sdk/dispatcher_pb2_grpc.py index 97b275892..ee412d3a7 100644 --- a/python-client/hatchet_sdk/dispatcher_pb2_grpc.py +++ b/python-client/hatchet_sdk/dispatcher_pb2_grpc.py @@ -2,7 +2,8 @@ """Client and server classes corresponding to protobuf-defined services.""" import grpc -# import dispatcher_pb2 as dispatcher__pb2 +# from .dispatcher_pb2 import dispatcher__pb2 + from . import dispatcher_pb2 as dispatcher__pb2 class DispatcherStub(object): diff --git a/python-client/hatchet_sdk/events_pb2.py b/python-client/hatchet_sdk/events_pb2.py index 9a5fbf577..da31c3c55 100644 --- a/python-client/hatchet_sdk/events_pb2.py +++ b/python-client/hatchet_sdk/events_pb2.py @@ -15,7 +15,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x65vents.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"|\n\x05\x45vent\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x0f\n\x07\x65ventId\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0f\n\x07payload\x18\x04 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"v\n\x10PushEventRequest\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x0f\n\x07payload\x18\x03 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"A\n\x10ListEventRequest\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x0b\n\x03key\x18\x03 \x01(\t\"+\n\x11ListEventResponse\x12\x16\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x06.Event\"7\n\x12ReplayEventRequest\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x0f\n\x07\x65ventId\x18\x02 \x01(\t2\x99\x01\n\rEventsService\x12#\n\x04Push\x12\x11.PushEventRequest\x1a\x06.Event\"\x00\x12/\n\x04List\x12\x11.ListEventRequest\x1a\x12.ListEventResponse\"\x00\x12\x32\n\x11ReplaySingleEvent\x12\x13.ReplayEventRequest\x1a\x06.Event\"\x00\x42GZEgithub.com/hatchet-dev/hatchet/internal/services/dispatcher/contractsb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x65vents.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"|\n\x05\x45vent\x12\x10\n\x08tenantId\x18\x01 \x01(\t\x12\x0f\n\x07\x65ventId\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0f\n\x07payload\x18\x04 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"d\n\x10PushEventRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\t\x12\x32\n\x0e\x65ventTimestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"/\n\x10ListEventRequest\x12\x0e\n\x06offset\x18\x01 \x01(\x05\x12\x0b\n\x03key\x18\x02 \x01(\t\"+\n\x11ListEventResponse\x12\x16\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x06.Event\"%\n\x12ReplayEventRequest\x12\x0f\n\x07\x65ventId\x18\x01 \x01(\t2\x99\x01\n\rEventsService\x12#\n\x04Push\x12\x11.PushEventRequest\x1a\x06.Event\"\x00\x12/\n\x04List\x12\x11.ListEventRequest\x1a\x12.ListEventResponse\"\x00\x12\x32\n\x11ReplaySingleEvent\x12\x13.ReplayEventRequest\x1a\x06.Event\"\x00\x42GZEgithub.com/hatchet-dev/hatchet/internal/services/dispatcher/contractsb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -26,13 +26,13 @@ _globals['_EVENT']._serialized_start=49 _globals['_EVENT']._serialized_end=173 _globals['_PUSHEVENTREQUEST']._serialized_start=175 - _globals['_PUSHEVENTREQUEST']._serialized_end=293 - _globals['_LISTEVENTREQUEST']._serialized_start=295 - _globals['_LISTEVENTREQUEST']._serialized_end=360 - _globals['_LISTEVENTRESPONSE']._serialized_start=362 - _globals['_LISTEVENTRESPONSE']._serialized_end=405 - _globals['_REPLAYEVENTREQUEST']._serialized_start=407 - _globals['_REPLAYEVENTREQUEST']._serialized_end=462 - _globals['_EVENTSSERVICE']._serialized_start=465 - _globals['_EVENTSSERVICE']._serialized_end=618 + _globals['_PUSHEVENTREQUEST']._serialized_end=275 + _globals['_LISTEVENTREQUEST']._serialized_start=277 + _globals['_LISTEVENTREQUEST']._serialized_end=324 + _globals['_LISTEVENTRESPONSE']._serialized_start=326 + _globals['_LISTEVENTRESPONSE']._serialized_end=369 + _globals['_REPLAYEVENTREQUEST']._serialized_start=371 + _globals['_REPLAYEVENTREQUEST']._serialized_end=408 + _globals['_EVENTSSERVICE']._serialized_start=411 + _globals['_EVENTSSERVICE']._serialized_end=564 # @@protoc_insertion_point(module_scope) diff --git a/python-client/hatchet_sdk/events_pb2.pyi b/python-client/hatchet_sdk/events_pb2.pyi index e04d1b36f..f118711d1 100644 --- a/python-client/hatchet_sdk/events_pb2.pyi +++ b/python-client/hatchet_sdk/events_pb2.pyi @@ -21,26 +21,22 @@ class Event(_message.Message): def __init__(self, tenantId: _Optional[str] = ..., eventId: _Optional[str] = ..., key: _Optional[str] = ..., payload: _Optional[str] = ..., eventTimestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... class PushEventRequest(_message.Message): - __slots__ = ("tenantId", "key", "payload", "eventTimestamp") - TENANTID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("key", "payload", "eventTimestamp") KEY_FIELD_NUMBER: _ClassVar[int] PAYLOAD_FIELD_NUMBER: _ClassVar[int] EVENTTIMESTAMP_FIELD_NUMBER: _ClassVar[int] - tenantId: str key: str payload: str eventTimestamp: _timestamp_pb2.Timestamp - def __init__(self, tenantId: _Optional[str] = ..., key: _Optional[str] = ..., payload: _Optional[str] = ..., eventTimestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + def __init__(self, key: _Optional[str] = ..., payload: _Optional[str] = ..., eventTimestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... class ListEventRequest(_message.Message): - __slots__ = ("tenantId", "offset", "key") - TENANTID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("offset", "key") OFFSET_FIELD_NUMBER: _ClassVar[int] KEY_FIELD_NUMBER: _ClassVar[int] - tenantId: str offset: int key: str - def __init__(self, tenantId: _Optional[str] = ..., offset: _Optional[int] = ..., key: _Optional[str] = ...) -> None: ... + def __init__(self, offset: _Optional[int] = ..., key: _Optional[str] = ...) -> None: ... class ListEventResponse(_message.Message): __slots__ = ("events",) @@ -49,9 +45,7 @@ class ListEventResponse(_message.Message): def __init__(self, events: _Optional[_Iterable[_Union[Event, _Mapping]]] = ...) -> None: ... class ReplayEventRequest(_message.Message): - __slots__ = ("tenantId", "eventId") - TENANTID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("eventId",) EVENTID_FIELD_NUMBER: _ClassVar[int] - tenantId: str eventId: str - def __init__(self, tenantId: _Optional[str] = ..., eventId: _Optional[str] = ...) -> None: ... + def __init__(self, eventId: _Optional[str] = ...) -> None: ... diff --git a/python-client/hatchet_sdk/events_pb2_grpc.py b/python-client/hatchet_sdk/events_pb2_grpc.py index b67297eeb..10783e381 100644 --- a/python-client/hatchet_sdk/events_pb2_grpc.py +++ b/python-client/hatchet_sdk/events_pb2_grpc.py @@ -2,7 +2,8 @@ """Client and server classes corresponding to protobuf-defined services.""" import grpc -# import events_pb2 as events__pb2 +# from .events_pb2 import events__pb2 + from . import events_pb2 as events__pb2 class EventsServiceStub(object): diff --git a/python-client/hatchet_sdk/loader.py b/python-client/hatchet_sdk/loader.py index efbc1c0da..9b715d8d8 100644 --- a/python-client/hatchet_sdk/loader.py +++ b/python-client/hatchet_sdk/loader.py @@ -3,17 +3,19 @@ from typing import Any, Optional, Dict class ClientTLSConfig: - def __init__(self, cert_file: str, key_file: str, ca_file: str, server_name: str): + def __init__(self, tls_strategy: str, cert_file: str, key_file: str, ca_file: str, server_name: str): + self.tls_strategy = tls_strategy self.cert_file = cert_file self.key_file = key_file self.ca_file = ca_file self.server_name = server_name class ClientConfig: - def __init__(self, tenant_id: str, tls_config: ClientTLSConfig, host_port: str="localhost:7070"): + def __init__(self, tenant_id: str, tls_config: ClientTLSConfig, token: str, host_port: str="localhost:7070"): self.tenant_id = tenant_id self.tls_config = tls_config self.host_port = host_port + self.token = token class ConfigLoader: def __init__(self, directory: str): @@ -30,20 +32,31 @@ def load_client_config(self) -> ClientConfig: if os.path.exists(config_file_path): with open(config_file_path, 'r') as file: config_data = yaml.safe_load(file) - - tls_config = self._load_tls_config(config_data['tls']) + tenant_id = config_data['tenantId'] if 'tenantId' in config_data else self._get_env_var('HATCHET_CLIENT_TENANT_ID') host_port = config_data['hostPort'] if 'hostPort' in config_data else self._get_env_var('HATCHET_CLIENT_HOST_PORT') + token = config_data['token'] if 'token' in config_data else self._get_env_var('HATCHET_CLIENT_TOKEN') + tls_config = self._load_tls_config(config_data['tls'], host_port) + + return ClientConfig(tenant_id, tls_config, token, host_port) - return ClientConfig(tenant_id, tls_config, host_port) + def _load_tls_config(self, tls_data: Dict, host_port) -> ClientTLSConfig: + tls_strategy = tls_data['tlsStrategy'] if 'tlsStrategy' in tls_data else self._get_env_var('HATCHET_CLIENT_TLS_STRATEGY') + + if not tls_strategy: + tls_strategy = 'tls' - def _load_tls_config(self, tls_data: Dict) -> ClientTLSConfig: cert_file = tls_data['tlsCertFile'] if 'tlsCertFile' in tls_data else self._get_env_var('HATCHET_CLIENT_TLS_CERT_FILE') key_file = tls_data['tlsKeyFile'] if 'tlsKeyFile' in tls_data else self._get_env_var('HATCHET_CLIENT_TLS_KEY_FILE') ca_file = tls_data['tlsRootCAFile'] if 'tlsRootCAFile' in tls_data else self._get_env_var('HATCHET_CLIENT_TLS_ROOT_CA_FILE') + server_name = tls_data['tlsServerName'] if 'tlsServerName' in tls_data else self._get_env_var('HATCHET_CLIENT_TLS_SERVER_NAME') - return ClientTLSConfig(cert_file, key_file, ca_file, server_name) + # if server_name is not set, use the host from the host_port + if not server_name: + server_name = host_port.split(':')[0] + + return ClientTLSConfig(tls_strategy, cert_file, key_file, ca_file, server_name) @staticmethod def _get_env_var(env_var: str, default: Optional[str] = None) -> str: diff --git a/python-client/hatchet_sdk/metadata.py b/python-client/hatchet_sdk/metadata.py new file mode 100644 index 000000000..49ec89234 --- /dev/null +++ b/python-client/hatchet_sdk/metadata.py @@ -0,0 +1,2 @@ +def get_metadata(token: str): + return [('authorization', 'bearer ' + token)] \ No newline at end of file diff --git a/python-client/hatchet_sdk/worker.py b/python-client/hatchet_sdk/worker.py index a8179126b..d68f554eb 100644 --- a/python-client/hatchet_sdk/worker.py +++ b/python-client/hatchet_sdk/worker.py @@ -100,7 +100,6 @@ def get_action_event(self, action : Action, event_type : ActionEventType) -> Act eventTimestamp.GetCurrentTime() return ActionEvent( - tenantId=action.tenant_id, workerId=action.worker_id, jobId=action.job_id, jobRunId=action.job_run_id, diff --git a/python-client/hatchet_sdk/workflows_pb2.py b/python-client/hatchet_sdk/workflows_pb2.py index e69c76efc..a7806f7ed 100644 --- a/python-client/hatchet_sdk/workflows_pb2.py +++ b/python-client/hatchet_sdk/workflows_pb2.py @@ -16,7 +16,7 @@ from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fworkflows.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"Q\n\x12PutWorkflowRequest\x12\x11\n\ttenant_id\x18\x01 \x01(\t\x12(\n\x04opts\x18\x02 \x01(\x0b\x32\x1a.CreateWorkflowVersionOpts\"\xdc\x01\n\x19\x43reateWorkflowVersionOpts\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x16\n\x0e\x65vent_triggers\x18\x04 \x03(\t\x12\x15\n\rcron_triggers\x18\x05 \x03(\t\x12\x36\n\x12scheduled_triggers\x18\x06 \x03(\x0b\x32\x1a.google.protobuf.Timestamp\x12$\n\x04jobs\x18\x07 \x03(\x0b\x32\x16.CreateWorkflowJobOpts\"s\n\x15\x43reateWorkflowJobOpts\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12&\n\x05steps\x18\x04 \x03(\x0b\x32\x17.CreateWorkflowStepOpts\"o\n\x16\x43reateWorkflowStepOpts\x12\x13\n\x0breadable_id\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12\x0e\n\x06inputs\x18\x04 \x01(\t\x12\x0f\n\x07parents\x18\x05 \x03(\t\")\n\x14ListWorkflowsRequest\x12\x11\n\ttenant_id\x18\x01 \x01(\t\"\x7f\n\x17ScheduleWorkflowRequest\x12\x11\n\ttenant_id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\x12-\n\tschedules\x18\x03 \x03(\x0b\x32\x1a.google.protobuf.Timestamp\x12\r\n\x05input\x18\x04 \x01(\t\"5\n\x15ListWorkflowsResponse\x12\x1c\n\tworkflows\x18\x01 \x03(\x0b\x32\t.Workflow\"D\n\x1cListWorkflowsForEventRequest\x12\x11\n\ttenant_id\x18\x01 \x01(\t\x12\x11\n\tevent_key\x18\x02 \x01(\t\"\xee\x01\n\x08Workflow\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x11\n\ttenant_id\x18\x05 \x01(\t\x12\x0c\n\x04name\x18\x06 \x01(\t\x12\x31\n\x0b\x64\x65scription\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\"\n\x08versions\x18\x08 \x03(\x0b\x32\x10.WorkflowVersion\"\xeb\x01\n\x0fWorkflowVersion\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07version\x18\x05 \x01(\t\x12\r\n\x05order\x18\x06 \x01(\x05\x12\x13\n\x0bworkflow_id\x18\x07 \x01(\t\x12#\n\x08triggers\x18\x08 \x01(\x0b\x32\x11.WorkflowTriggers\x12\x12\n\x04jobs\x18\t \x03(\x0b\x32\x04.Job\"\x80\x02\n\x10WorkflowTriggers\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1b\n\x13workflow_version_id\x18\x05 \x01(\t\x12\x11\n\ttenant_id\x18\x06 \x01(\t\x12(\n\x06\x65vents\x18\x07 \x03(\x0b\x32\x18.WorkflowTriggerEventRef\x12&\n\x05\x63rons\x18\x08 \x03(\x0b\x32\x17.WorkflowTriggerCronRef\"?\n\x17WorkflowTriggerEventRef\x12\x11\n\tparent_id\x18\x01 \x01(\t\x12\x11\n\tevent_key\x18\x02 \x01(\t\"9\n\x16WorkflowTriggerCronRef\x12\x11\n\tparent_id\x18\x01 \x01(\t\x12\x0c\n\x04\x63ron\x18\x02 \x01(\t\"\xa7\x02\n\x03Job\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x11\n\ttenant_id\x18\x05 \x01(\t\x12\x1b\n\x13workflow_version_id\x18\x06 \x01(\t\x12\x0c\n\x04name\x18\x07 \x01(\t\x12\x31\n\x0b\x64\x65scription\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x14\n\x05steps\x18\t \x03(\x0b\x32\x05.Step\x12-\n\x07timeout\x18\n \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xaa\x02\n\x04Step\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x31\n\x0breadable_id\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x11\n\ttenant_id\x18\x06 \x01(\t\x12\x0e\n\x06job_id\x18\x07 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x08 \x01(\t\x12-\n\x07timeout\x18\t \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x0f\n\x07parents\x18\n \x03(\t\x12\x10\n\x08\x63hildren\x18\x0b \x03(\t\"?\n\x15\x44\x65leteWorkflowRequest\x12\x11\n\ttenant_id\x18\x01 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x02 \x01(\t\";\n\x18GetWorkflowByNameRequest\x12\x11\n\ttenant_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t2\x87\x03\n\x0fWorkflowService\x12>\n\rListWorkflows\x12\x15.ListWorkflowsRequest\x1a\x16.ListWorkflowsResponse\x12\x34\n\x0bPutWorkflow\x12\x13.PutWorkflowRequest\x1a\x10.WorkflowVersion\x12>\n\x10ScheduleWorkflow\x12\x18.ScheduleWorkflowRequest\x1a\x10.WorkflowVersion\x12\x39\n\x11GetWorkflowByName\x12\x19.GetWorkflowByNameRequest\x1a\t.Workflow\x12N\n\x15ListWorkflowsForEvent\x12\x1d.ListWorkflowsForEventRequest\x1a\x16.ListWorkflowsResponse\x12\x33\n\x0e\x44\x65leteWorkflow\x12\x16.DeleteWorkflowRequest\x1a\t.WorkflowBBZ@github.com/hatchet-dev/hatchet/internal/services/admin/contractsb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fworkflows.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\">\n\x12PutWorkflowRequest\x12(\n\x04opts\x18\x01 \x01(\x0b\x32\x1a.CreateWorkflowVersionOpts\"\xdc\x01\n\x19\x43reateWorkflowVersionOpts\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x16\n\x0e\x65vent_triggers\x18\x04 \x03(\t\x12\x15\n\rcron_triggers\x18\x05 \x03(\t\x12\x36\n\x12scheduled_triggers\x18\x06 \x03(\x0b\x32\x1a.google.protobuf.Timestamp\x12$\n\x04jobs\x18\x07 \x03(\x0b\x32\x16.CreateWorkflowJobOpts\"s\n\x15\x43reateWorkflowJobOpts\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12&\n\x05steps\x18\x04 \x03(\x0b\x32\x17.CreateWorkflowStepOpts\"o\n\x16\x43reateWorkflowStepOpts\x12\x13\n\x0breadable_id\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x0f\n\x07timeout\x18\x03 \x01(\t\x12\x0e\n\x06inputs\x18\x04 \x01(\t\x12\x0f\n\x07parents\x18\x05 \x03(\t\"\x16\n\x14ListWorkflowsRequest\"l\n\x17ScheduleWorkflowRequest\x12\x13\n\x0bworkflow_id\x18\x01 \x01(\t\x12-\n\tschedules\x18\x02 \x03(\x0b\x32\x1a.google.protobuf.Timestamp\x12\r\n\x05input\x18\x03 \x01(\t\"5\n\x15ListWorkflowsResponse\x12\x1c\n\tworkflows\x18\x01 \x03(\x0b\x32\t.Workflow\"1\n\x1cListWorkflowsForEventRequest\x12\x11\n\tevent_key\x18\x01 \x01(\t\"\xee\x01\n\x08Workflow\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x11\n\ttenant_id\x18\x05 \x01(\t\x12\x0c\n\x04name\x18\x06 \x01(\t\x12\x31\n\x0b\x64\x65scription\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\"\n\x08versions\x18\x08 \x03(\x0b\x32\x10.WorkflowVersion\"\xeb\x01\n\x0fWorkflowVersion\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07version\x18\x05 \x01(\t\x12\r\n\x05order\x18\x06 \x01(\x05\x12\x13\n\x0bworkflow_id\x18\x07 \x01(\t\x12#\n\x08triggers\x18\x08 \x01(\x0b\x32\x11.WorkflowTriggers\x12\x12\n\x04jobs\x18\t \x03(\x0b\x32\x04.Job\"\x80\x02\n\x10WorkflowTriggers\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1b\n\x13workflow_version_id\x18\x05 \x01(\t\x12\x11\n\ttenant_id\x18\x06 \x01(\t\x12(\n\x06\x65vents\x18\x07 \x03(\x0b\x32\x18.WorkflowTriggerEventRef\x12&\n\x05\x63rons\x18\x08 \x03(\x0b\x32\x17.WorkflowTriggerCronRef\"?\n\x17WorkflowTriggerEventRef\x12\x11\n\tparent_id\x18\x01 \x01(\t\x12\x11\n\tevent_key\x18\x02 \x01(\t\"9\n\x16WorkflowTriggerCronRef\x12\x11\n\tparent_id\x18\x01 \x01(\t\x12\x0c\n\x04\x63ron\x18\x02 \x01(\t\"\xa7\x02\n\x03Job\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x11\n\ttenant_id\x18\x05 \x01(\t\x12\x1b\n\x13workflow_version_id\x18\x06 \x01(\t\x12\x0c\n\x04name\x18\x07 \x01(\t\x12\x31\n\x0b\x64\x65scription\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x14\n\x05steps\x18\t \x03(\x0b\x32\x05.Step\x12-\n\x07timeout\x18\n \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\xaa\x02\n\x04Step\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x31\n\x0breadable_id\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x11\n\ttenant_id\x18\x06 \x01(\t\x12\x0e\n\x06job_id\x18\x07 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x08 \x01(\t\x12-\n\x07timeout\x18\t \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x0f\n\x07parents\x18\n \x03(\t\x12\x10\n\x08\x63hildren\x18\x0b \x03(\t\",\n\x15\x44\x65leteWorkflowRequest\x12\x13\n\x0bworkflow_id\x18\x01 \x01(\t\"(\n\x18GetWorkflowByNameRequest\x12\x0c\n\x04name\x18\x01 \x01(\t2\x87\x03\n\x0fWorkflowService\x12>\n\rListWorkflows\x12\x15.ListWorkflowsRequest\x1a\x16.ListWorkflowsResponse\x12\x34\n\x0bPutWorkflow\x12\x13.PutWorkflowRequest\x1a\x10.WorkflowVersion\x12>\n\x10ScheduleWorkflow\x12\x18.ScheduleWorkflowRequest\x1a\x10.WorkflowVersion\x12\x39\n\x11GetWorkflowByName\x12\x19.GetWorkflowByNameRequest\x1a\t.Workflow\x12N\n\x15ListWorkflowsForEvent\x12\x1d.ListWorkflowsForEventRequest\x1a\x16.ListWorkflowsResponse\x12\x33\n\x0e\x44\x65leteWorkflow\x12\x16.DeleteWorkflowRequest\x1a\t.WorkflowBBZ@github.com/hatchet-dev/hatchet/internal/services/admin/contractsb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -25,39 +25,39 @@ _globals['DESCRIPTOR']._options = None _globals['DESCRIPTOR']._serialized_options = b'Z@github.com/hatchet-dev/hatchet/internal/services/admin/contracts' _globals['_PUTWORKFLOWREQUEST']._serialized_start=84 - _globals['_PUTWORKFLOWREQUEST']._serialized_end=165 - _globals['_CREATEWORKFLOWVERSIONOPTS']._serialized_start=168 - _globals['_CREATEWORKFLOWVERSIONOPTS']._serialized_end=388 - _globals['_CREATEWORKFLOWJOBOPTS']._serialized_start=390 - _globals['_CREATEWORKFLOWJOBOPTS']._serialized_end=505 - _globals['_CREATEWORKFLOWSTEPOPTS']._serialized_start=507 - _globals['_CREATEWORKFLOWSTEPOPTS']._serialized_end=618 - _globals['_LISTWORKFLOWSREQUEST']._serialized_start=620 - _globals['_LISTWORKFLOWSREQUEST']._serialized_end=661 - _globals['_SCHEDULEWORKFLOWREQUEST']._serialized_start=663 - _globals['_SCHEDULEWORKFLOWREQUEST']._serialized_end=790 - _globals['_LISTWORKFLOWSRESPONSE']._serialized_start=792 - _globals['_LISTWORKFLOWSRESPONSE']._serialized_end=845 - _globals['_LISTWORKFLOWSFOREVENTREQUEST']._serialized_start=847 - _globals['_LISTWORKFLOWSFOREVENTREQUEST']._serialized_end=915 - _globals['_WORKFLOW']._serialized_start=918 - _globals['_WORKFLOW']._serialized_end=1156 - _globals['_WORKFLOWVERSION']._serialized_start=1159 - _globals['_WORKFLOWVERSION']._serialized_end=1394 - _globals['_WORKFLOWTRIGGERS']._serialized_start=1397 - _globals['_WORKFLOWTRIGGERS']._serialized_end=1653 - _globals['_WORKFLOWTRIGGEREVENTREF']._serialized_start=1655 - _globals['_WORKFLOWTRIGGEREVENTREF']._serialized_end=1718 - _globals['_WORKFLOWTRIGGERCRONREF']._serialized_start=1720 - _globals['_WORKFLOWTRIGGERCRONREF']._serialized_end=1777 - _globals['_JOB']._serialized_start=1780 - _globals['_JOB']._serialized_end=2075 - _globals['_STEP']._serialized_start=2078 - _globals['_STEP']._serialized_end=2376 - _globals['_DELETEWORKFLOWREQUEST']._serialized_start=2378 - _globals['_DELETEWORKFLOWREQUEST']._serialized_end=2441 - _globals['_GETWORKFLOWBYNAMEREQUEST']._serialized_start=2443 - _globals['_GETWORKFLOWBYNAMEREQUEST']._serialized_end=2502 - _globals['_WORKFLOWSERVICE']._serialized_start=2505 - _globals['_WORKFLOWSERVICE']._serialized_end=2896 + _globals['_PUTWORKFLOWREQUEST']._serialized_end=146 + _globals['_CREATEWORKFLOWVERSIONOPTS']._serialized_start=149 + _globals['_CREATEWORKFLOWVERSIONOPTS']._serialized_end=369 + _globals['_CREATEWORKFLOWJOBOPTS']._serialized_start=371 + _globals['_CREATEWORKFLOWJOBOPTS']._serialized_end=486 + _globals['_CREATEWORKFLOWSTEPOPTS']._serialized_start=488 + _globals['_CREATEWORKFLOWSTEPOPTS']._serialized_end=599 + _globals['_LISTWORKFLOWSREQUEST']._serialized_start=601 + _globals['_LISTWORKFLOWSREQUEST']._serialized_end=623 + _globals['_SCHEDULEWORKFLOWREQUEST']._serialized_start=625 + _globals['_SCHEDULEWORKFLOWREQUEST']._serialized_end=733 + _globals['_LISTWORKFLOWSRESPONSE']._serialized_start=735 + _globals['_LISTWORKFLOWSRESPONSE']._serialized_end=788 + _globals['_LISTWORKFLOWSFOREVENTREQUEST']._serialized_start=790 + _globals['_LISTWORKFLOWSFOREVENTREQUEST']._serialized_end=839 + _globals['_WORKFLOW']._serialized_start=842 + _globals['_WORKFLOW']._serialized_end=1080 + _globals['_WORKFLOWVERSION']._serialized_start=1083 + _globals['_WORKFLOWVERSION']._serialized_end=1318 + _globals['_WORKFLOWTRIGGERS']._serialized_start=1321 + _globals['_WORKFLOWTRIGGERS']._serialized_end=1577 + _globals['_WORKFLOWTRIGGEREVENTREF']._serialized_start=1579 + _globals['_WORKFLOWTRIGGEREVENTREF']._serialized_end=1642 + _globals['_WORKFLOWTRIGGERCRONREF']._serialized_start=1644 + _globals['_WORKFLOWTRIGGERCRONREF']._serialized_end=1701 + _globals['_JOB']._serialized_start=1704 + _globals['_JOB']._serialized_end=1999 + _globals['_STEP']._serialized_start=2002 + _globals['_STEP']._serialized_end=2300 + _globals['_DELETEWORKFLOWREQUEST']._serialized_start=2302 + _globals['_DELETEWORKFLOWREQUEST']._serialized_end=2346 + _globals['_GETWORKFLOWBYNAMEREQUEST']._serialized_start=2348 + _globals['_GETWORKFLOWBYNAMEREQUEST']._serialized_end=2388 + _globals['_WORKFLOWSERVICE']._serialized_start=2391 + _globals['_WORKFLOWSERVICE']._serialized_end=2782 # @@protoc_insertion_point(module_scope) diff --git a/python-client/hatchet_sdk/workflows_pb2.pyi b/python-client/hatchet_sdk/workflows_pb2.pyi index 61df41f05..e6dc4ad5c 100644 --- a/python-client/hatchet_sdk/workflows_pb2.pyi +++ b/python-client/hatchet_sdk/workflows_pb2.pyi @@ -8,12 +8,10 @@ from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Map DESCRIPTOR: _descriptor.FileDescriptor class PutWorkflowRequest(_message.Message): - __slots__ = ("tenant_id", "opts") - TENANT_ID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("opts",) OPTS_FIELD_NUMBER: _ClassVar[int] - tenant_id: str opts: CreateWorkflowVersionOpts - def __init__(self, tenant_id: _Optional[str] = ..., opts: _Optional[_Union[CreateWorkflowVersionOpts, _Mapping]] = ...) -> None: ... + def __init__(self, opts: _Optional[_Union[CreateWorkflowVersionOpts, _Mapping]] = ...) -> None: ... class CreateWorkflowVersionOpts(_message.Message): __slots__ = ("name", "description", "version", "event_triggers", "cron_triggers", "scheduled_triggers", "jobs") @@ -60,22 +58,18 @@ class CreateWorkflowStepOpts(_message.Message): def __init__(self, readable_id: _Optional[str] = ..., action: _Optional[str] = ..., timeout: _Optional[str] = ..., inputs: _Optional[str] = ..., parents: _Optional[_Iterable[str]] = ...) -> None: ... class ListWorkflowsRequest(_message.Message): - __slots__ = ("tenant_id",) - TENANT_ID_FIELD_NUMBER: _ClassVar[int] - tenant_id: str - def __init__(self, tenant_id: _Optional[str] = ...) -> None: ... + __slots__ = () + def __init__(self) -> None: ... class ScheduleWorkflowRequest(_message.Message): - __slots__ = ("tenant_id", "workflow_id", "schedules", "input") - TENANT_ID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("workflow_id", "schedules", "input") WORKFLOW_ID_FIELD_NUMBER: _ClassVar[int] SCHEDULES_FIELD_NUMBER: _ClassVar[int] INPUT_FIELD_NUMBER: _ClassVar[int] - tenant_id: str workflow_id: str schedules: _containers.RepeatedCompositeFieldContainer[_timestamp_pb2.Timestamp] input: str - def __init__(self, tenant_id: _Optional[str] = ..., workflow_id: _Optional[str] = ..., schedules: _Optional[_Iterable[_Union[_timestamp_pb2.Timestamp, _Mapping]]] = ..., input: _Optional[str] = ...) -> None: ... + def __init__(self, workflow_id: _Optional[str] = ..., schedules: _Optional[_Iterable[_Union[_timestamp_pb2.Timestamp, _Mapping]]] = ..., input: _Optional[str] = ...) -> None: ... class ListWorkflowsResponse(_message.Message): __slots__ = ("workflows",) @@ -84,12 +78,10 @@ class ListWorkflowsResponse(_message.Message): def __init__(self, workflows: _Optional[_Iterable[_Union[Workflow, _Mapping]]] = ...) -> None: ... class ListWorkflowsForEventRequest(_message.Message): - __slots__ = ("tenant_id", "event_key") - TENANT_ID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("event_key",) EVENT_KEY_FIELD_NUMBER: _ClassVar[int] - tenant_id: str event_key: str - def __init__(self, tenant_id: _Optional[str] = ..., event_key: _Optional[str] = ...) -> None: ... + def __init__(self, event_key: _Optional[str] = ...) -> None: ... class Workflow(_message.Message): __slots__ = ("id", "created_at", "updated_at", "tenant_id", "name", "description", "versions") @@ -210,17 +202,13 @@ class Step(_message.Message): def __init__(self, id: _Optional[str] = ..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., updated_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., readable_id: _Optional[_Union[_wrappers_pb2.StringValue, _Mapping]] = ..., tenant_id: _Optional[str] = ..., job_id: _Optional[str] = ..., action: _Optional[str] = ..., timeout: _Optional[_Union[_wrappers_pb2.StringValue, _Mapping]] = ..., parents: _Optional[_Iterable[str]] = ..., children: _Optional[_Iterable[str]] = ...) -> None: ... class DeleteWorkflowRequest(_message.Message): - __slots__ = ("tenant_id", "workflow_id") - TENANT_ID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("workflow_id",) WORKFLOW_ID_FIELD_NUMBER: _ClassVar[int] - tenant_id: str workflow_id: str - def __init__(self, tenant_id: _Optional[str] = ..., workflow_id: _Optional[str] = ...) -> None: ... + def __init__(self, workflow_id: _Optional[str] = ...) -> None: ... class GetWorkflowByNameRequest(_message.Message): - __slots__ = ("tenant_id", "name") - TENANT_ID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("name",) NAME_FIELD_NUMBER: _ClassVar[int] - tenant_id: str name: str - def __init__(self, tenant_id: _Optional[str] = ..., name: _Optional[str] = ...) -> None: ... + def __init__(self, name: _Optional[str] = ...) -> None: ... diff --git a/python-client/hatchet_sdk/workflows_pb2_grpc.py b/python-client/hatchet_sdk/workflows_pb2_grpc.py index 0d7f8f25b..2a08cbf4a 100644 --- a/python-client/hatchet_sdk/workflows_pb2_grpc.py +++ b/python-client/hatchet_sdk/workflows_pb2_grpc.py @@ -2,7 +2,9 @@ """Client and server classes corresponding to protobuf-defined services.""" import grpc -from . import workflows_pb2 as workflows__pb2 +# from .workflows_pb2 import workflows__pb2 + +from . import workflows_pb2 as workflows__pb2 class WorkflowServiceStub(object): """WorkflowService represents a set of RPCs for managing workflows. From 75293f84dda6cc5dd477fa41067d916d1c47f184 Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Thu, 25 Jan 2024 20:37:20 -0800 Subject: [PATCH 3/9] chore: add api token generation command --- .github/workflows/test.yml | 7 ++- CONTRIBUTING.md | 12 +++++ cmd/hatchet-admin/cli/keyset.go | 13 ++---- cmd/hatchet-admin/cli/seed.go | 2 + cmd/hatchet-admin/cli/token.go | 76 ++++++++++++++++++++++++++++++++ internal/config/loader/loader.go | 3 +- 6 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 cmd/hatchet-admin/cli/token.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e6aec616..48326e1f5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -153,14 +153,17 @@ jobs: . .env set +a - go run ./cmd/hatchet-admin seed + go run ./cmd/hatchet-admin quickstart go run ./cmd/hatchet-engine & go run ./cmd/hatchet-api & sleep 30 - name: Test - run: go test -tags e2e ./... -p 1 -v -failfast + run: | + export HATCHET_CLIENT_TOKEN="$(go run ./cmd/hatchet-admin token create --tenant-id 707d0855-80ab-4e1f-a156-f1c4546cbf52)" + + go test -tags e2e ./... -p 1 -v -failfast - name: Teardown run: docker compose down diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad9a30569..a5f185cae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,3 +105,15 @@ Generate a service account in GCP which can encrypt/decrypt on CloudKMS, then do ``` SERVER_ENCRYPTION_CLOUDKMS_CREDENTIALS_JSON='{...}' ``` + +## Issues + +### Query engine leakage + +Sometimes the spawned query engines from Prisma don't get killed when hot reloading. You can run `task kill-query-engines` on OSX to kill the query engines. + +Make sure you call `.Disconnect` on the database config object when writing CLI commands which interact with the database. If you don't, and you try to wrap these CLI commands in a new command, it will never exit, for example: + +``` +export HATCHET_CLIENT_TOKEN="$(go run ./cmd/hatchet-admin token create --tenant-id )" +``` diff --git a/cmd/hatchet-admin/cli/keyset.go b/cmd/hatchet-admin/cli/keyset.go index a7ab16be1..0779ca95a 100644 --- a/cmd/hatchet-admin/cli/keyset.go +++ b/cmd/hatchet-admin/cli/keyset.go @@ -14,7 +14,6 @@ var ( cloudKMSKeyURI string ) -// keysetCmd seeds the database with initial data var keysetCmd = &cobra.Command{ Use: "keyset", Short: "command for managing keysets.", @@ -24,9 +23,7 @@ var keysetCreateMasterCmd = &cobra.Command{ Use: "create-master", Short: "create a new local master keyset.", Run: func(cmd *cobra.Command, args []string) { - var err error - - err = runCreateLocalMasterKeyset() + err := runCreateLocalMasterKeyset() if err != nil { fmt.Printf("Fatal: could not run seed command: %v\n", err) @@ -39,9 +36,7 @@ var keysetCreateLocalJWTCmd = &cobra.Command{ Use: "create-local-jwt", Short: "create a new local JWT keyset.", Run: func(cmd *cobra.Command, args []string) { - var err error - - err = runCreateLocalJWTKeyset() + err := runCreateLocalJWTKeyset() if err != nil { fmt.Printf("Fatal: could not run seed command: %v\n", err) @@ -54,9 +49,7 @@ var keysetCreateCloudKMSJWTCmd = &cobra.Command{ Use: "create-cloudkms-jwt", Short: "create a new JWT keyset encrypted by a remote CloudKMS repository.", Run: func(cmd *cobra.Command, args []string) { - var err error - - err = runCreateCloudKMSJWTKeyset() + err := runCreateCloudKMSJWTKeyset() if err != nil { fmt.Printf("Fatal: could not run seed command: %v\n", err) diff --git a/cmd/hatchet-admin/cli/seed.go b/cmd/hatchet-admin/cli/seed.go index 99d82bdbd..3564a0223 100644 --- a/cmd/hatchet-admin/cli/seed.go +++ b/cmd/hatchet-admin/cli/seed.go @@ -41,6 +41,8 @@ func runSeed(cf *loader.ConfigLoader) error { panic(err) } + defer dc.Disconnect() // nolint: errcheck + shouldSeedUser := dc.Seed.AdminEmail != "" && dc.Seed.AdminPassword != "" var userId string diff --git a/cmd/hatchet-admin/cli/token.go b/cmd/hatchet-admin/cli/token.go new file mode 100644 index 000000000..232afa3e4 --- /dev/null +++ b/cmd/hatchet-admin/cli/token.go @@ -0,0 +1,76 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/hatchet-dev/hatchet/internal/config/loader" +) + +var ( + tokenTenantId string + tokenName string +) + +var tokenCmd = &cobra.Command{ + Use: "token", + Short: "command for generating tokens.", +} + +var tokenCreateAPICmd = &cobra.Command{ + Use: "create", + Short: "create a new API token.", + Run: func(cmd *cobra.Command, args []string) { + err := runCreateAPIToken() + + if err != nil { + fmt.Printf("Fatal: could not run seed command: %v\n", err) + } + }, +} + +func init() { + rootCmd.AddCommand(tokenCmd) + tokenCmd.AddCommand(tokenCreateAPICmd) + + tokenCreateAPICmd.PersistentFlags().StringVar( + &tokenTenantId, + "tenant-id", + "", + "the tenant ID to associate with the token", + ) + + // require the tenant ID + tokenCreateAPICmd.MarkPersistentFlagRequired("tenant-id") // nolint: errcheck + + tokenCreateAPICmd.PersistentFlags().StringVar( + &tokenName, + "name", + "default", + "the name of the token", + ) +} + +func runCreateAPIToken() error { + // read in the local config + configLoader := loader.NewConfigLoader(configDirectory) + + serverConf, err := configLoader.LoadServerConfig() + + if err != nil { + return err + } + + defer serverConf.Disconnect() // nolint: errcheck + + defaultTok, err := serverConf.Auth.JWTManager.GenerateTenantToken(tokenTenantId, tokenName) + + if err != nil { + return err + } + + fmt.Println(defaultTok) + + return nil +} diff --git a/internal/config/loader/loader.go b/internal/config/loader/loader.go index 44867bbd9..11444a665 100644 --- a/internal/config/loader/loader.go +++ b/internal/config/loader/loader.go @@ -5,6 +5,7 @@ package loader import ( "context" "fmt" + "os" "path/filepath" "strings" @@ -108,7 +109,7 @@ func GetDatabaseConfigFromConfigFile(cf *database.ConfigFile) (res *database.Con cf.PostgresSSLMode, ) - // os.Setenv("DATABASE_URL", databaseUrl) + os.Setenv("DATABASE_URL", databaseUrl) client := db.NewClient( // db.WithDatasourceURL(databaseUrl), From 464be8d06122ae6945b44191cf9b126092658ce0 Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Thu, 25 Jan 2024 20:47:59 -0800 Subject: [PATCH 4/9] fix: e2e tests --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 48326e1f5..3b8d088b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -155,13 +155,13 @@ jobs: go run ./cmd/hatchet-admin quickstart - go run ./cmd/hatchet-engine & - go run ./cmd/hatchet-api & + go run ./cmd/hatchet-engine --config ./generated/ & + go run ./cmd/hatchet-api --config ./generated/ & sleep 30 - name: Test run: | - export HATCHET_CLIENT_TOKEN="$(go run ./cmd/hatchet-admin token create --tenant-id 707d0855-80ab-4e1f-a156-f1c4546cbf52)" + export HATCHET_CLIENT_TOKEN="$(go run ./cmd/hatchet-admin token create --config ./generated/ --tenant-id 707d0855-80ab-4e1f-a156-f1c4546cbf52)" go test -tags e2e ./... -p 1 -v -failfast From 462e6157deca43af3e290e20ef43372f1872737f Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Thu, 25 Jan 2024 20:48:54 -0800 Subject: [PATCH 5/9] chore: set timeout for e2e job --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3b8d088b0..4617714eb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -102,6 +102,7 @@ jobs: e2e: runs-on: ubuntu-latest + timeout-minutes: 5 env: DATABASE_URL: postgresql://hatchet:hatchet@127.0.0.1:5431/hatchet From 3ceace5bab1ce562b9ae1bdef7ffa90ba9eadd01 Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Thu, 25 Jan 2024 21:00:39 -0800 Subject: [PATCH 6/9] fix: e2e tests, remove client-side certs --- internal/config/loader/loaderutils/tls.go | 4 ++++ internal/testutils/env.go | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/config/loader/loaderutils/tls.go b/internal/config/loader/loaderutils/tls.go index 7004cff8b..9e204760a 100644 --- a/internal/config/loader/loaderutils/tls.go +++ b/internal/config/loader/loaderutils/tls.go @@ -44,6 +44,10 @@ func LoadServerTLSConfig(tlsConfig *shared.TLSConfigFile) (*tls.Config, error) { switch tlsConfig.TLSStrategy { case "tls": res.ClientAuth = tls.VerifyClientCertIfGiven + + if ca != nil { + res.ClientCAs = ca + } case "mtls": if ca == nil { return nil, fmt.Errorf("Client CA is required for mTLS") diff --git a/internal/testutils/env.go b/internal/testutils/env.go index e1830ae4c..998537e7c 100644 --- a/internal/testutils/env.go +++ b/internal/testutils/env.go @@ -11,7 +11,5 @@ func Prepare(t *testing.T) { _ = os.Setenv("HATCHET_CLIENT_TENANT_ID", "707d0855-80ab-4e1f-a156-f1c4546cbf52") _ = os.Setenv("DATABASE_URL", "postgresql://hatchet:hatchet@127.0.0.1:5431/hatchet") _ = os.Setenv("HATCHET_CLIENT_TLS_ROOT_CA_FILE", "../../hack/dev/certs/ca.cert") - _ = os.Setenv("HATCHET_CLIENT_TLS_CERT_FILE", "../../hack/dev/certs/client-worker.pem") - _ = os.Setenv("HATCHET_CLIENT_TLS_KEY_FILE", "../../hack/dev/certs/client-worker.key") _ = os.Setenv("HATCHET_CLIENT_TLS_SERVER_NAME", "cluster") } From 6489b35efe6b5d3f9be0c70864bf04eaf976cbc5 Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Fri, 26 Jan 2024 10:17:00 -0800 Subject: [PATCH 7/9] chore: address PR review comments --- .gitignore | 2 + CONTRIBUTING.md | 40 ++++- Taskfile.yaml | 11 +- api/v1/server/authn/middleware.go | 2 - api/v1/server/authz/middleware.go | 28 ++-- api/v1/server/handlers/api-tokens/list.go | 1 - api/v1/server/handlers/api-tokens/revoke.go | 2 +- cmd/hatchet-admin/cli/keyset.go | 77 +++++----- cmd/hatchet-admin/cli/quickstart.go | 1 - cmd/hatchet-admin/cli/token.go | 2 +- hack/dev/generate-dev-api-token.sh | 10 ++ hack/dev/generate-local-encryption-keys.sh | 17 +++ ...mporal-certs.sh => generate-x509-certs.sh} | 0 hack/dev/start-temporal-server.sh | 9 -- hack/dev/write-default-env.sh | 33 ----- internal/auth/token/token.go | 27 ++-- internal/config/loader/loader.go | 138 +++++++++++++----- internal/config/loader/loaderutils/files.go | 14 ++ internal/config/server/server.go | 14 +- internal/encryption/cloudkms_test.go | 2 - internal/encryption/envelope.go | 38 ----- internal/repository/prisma/dbsqlc/models.go | 8 +- internal/repository/prisma/dbsqlc/schema.sql | 10 +- .../20240126173545_v0_7_0/migration.sql | 72 +++++++++ 24 files changed, 341 insertions(+), 217 deletions(-) create mode 100644 hack/dev/generate-dev-api-token.sh create mode 100644 hack/dev/generate-local-encryption-keys.sh rename hack/dev/{generate-temporal-certs.sh => generate-x509-certs.sh} (100%) delete mode 100644 hack/dev/start-temporal-server.sh delete mode 100644 hack/dev/write-default-env.sh delete mode 100644 internal/encryption/envelope.go create mode 100644 prisma/migrations/20240126173545_v0_7_0/migration.sql diff --git a/.gitignore b/.gitignore index e8e389247..0bda84a76 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,5 @@ tmp postgres-data rabbitmq.conf + +*encryption-keys \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a5f185cae..df13d34bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,9 @@ 3. Generate certificates needed for communicating between the Hatchet client and engine: `task generate-certs` -4. Create environment variables: +4. Generate keysets for encryption: `task generate-local-encryption-keys` + +5. Create environment variables: ```sh alias randstring='f() { openssl rand -base64 69 | tr -d "\n" | tr -d "=+/" | cut -c1-$1 };f' @@ -30,6 +32,10 @@ SERVER_TLS_CERT_FILE=./hack/dev/certs/cluster.pem SERVER_TLS_KEY_FILE=./hack/dev/certs/cluster.key SERVER_TLS_ROOT_CA_FILE=./hack/dev/certs/ca.cert +SERVER_ENCRYPTION_MASTER_KEYSET_FILE=./hack/dev/encryption-keys/master.key +SERVER_ENCRYPTION_JWT_PRIVATE_KEYSET_FILE=./hack/dev/encryption-keys/private_ec256.key +SERVER_ENCRYPTION_JWT_PUBLIC_KEYSET_FILE=./hack/dev/encryption-keys/public_ec256.key + SERVER_PORT=8080 SERVER_URL=https://app.dev.hatchet-tools.com @@ -37,22 +43,44 @@ SERVER_AUTH_COOKIE_SECRETS="$(randstring 16) $(randstring 16)" SERVER_AUTH_COOKIE_DOMAIN=app.dev.hatchet-tools.com SERVER_AUTH_COOKIE_INSECURE=false SERVER_AUTH_SET_EMAIL_VERIFIED=true + +SERVER_LOGGER_LEVEL=debug +SERVER_LOGGER_FORMAT=console +DATABASE_LOGGER_LEVEL=debug +DATABASE_LOGGER_FORMAT=console EOF ``` -5. Migrate the database: `task prisma-migrate` +6. Migrate the database: `task prisma-migrate` -6. Generate all files: `task generate` +7. Generate all files: `task generate` -7. Seed the database: `task seed-dev` +8. Seed the database: `task seed-dev` -8. Start the Hatchet engine, API server, dashboard, and Prisma studio: +9. Start the Hatchet engine, API server, dashboard, and Prisma studio: ```sh task start-dev ``` -10. To create and test workflows, run the examples in the `./examples` directory. You will need to add the tenant (output from the `task seed-dev` command) to the `.env` file in each example directory. +### Creating and testing workflows + +To create and test workflows, run the examples in the `./examples` directory. + +You will need to add the tenant (output from the `task seed-dev` command) to the `.env` file in each example directory. An example `.env` file for the `./examples/simple` directory can be generated via: + +```sh +alias get_token='go run ./cmd/hatchet-admin token create --name local --tenant-id 707d0855-80ab-4e1f-a156-f1c4546cbf52' + +cat > ./examples/simple/.env <`, -// and returns the token if it exists. func getBearerTokenFromRequest(r *http.Request) (string, error) { reqToken := r.Header.Get("Authorization") splitToken := strings.Split(reqToken, "Bearer") diff --git a/api/v1/server/authz/middleware.go b/api/v1/server/authz/middleware.go index 90c8cd085..40cb23bd2 100644 --- a/api/v1/server/authz/middleware.go +++ b/api/v1/server/authz/middleware.go @@ -42,31 +42,17 @@ func (a *AuthZ) authorize(c echo.Context, r *middleware.RouteInfo) error { } var err error - var checkedAuthz bool - if r.Security.CookieAuth() && c.Get("auth_strategy").(string) == "cookie" { + switch c.Get("auth_strategy").(string) { + case "cookie": err = a.handleCookieAuth(c, r) - checkedAuthz = true - } - - if err != nil { - return err - } - - if r.Security.BearerAuth() && c.Get("auth_strategy").(string) == "bearer" { + case "bearer": err = a.handleBearerAuth(c, r) - checkedAuthz = true - } - - if err != nil { - return err - } - - if !checkedAuthz { + default: return echo.NewHTTPError(http.StatusInternalServerError, "No authorization strategy was checked") } - return nil + return err } func (a *AuthZ) handleCookieAuth(c echo.Context, r *middleware.RouteInfo) error { @@ -162,6 +148,10 @@ var adminAndOwnerOnly = []string{ "TenantInviteUpdate", "TenantInviteDelete", "TenantMemberList", + // members cannot create API tokens for a tenant, because they have admin permissions + "ApiTokenList", + "ApiTokenCreate", + "ApiTokenUpdateRevoke", } func (a *AuthZ) authorizeTenantOperations(tenant *db.TenantModel, tenantMember *db.TenantMemberModel, r *middleware.RouteInfo) error { diff --git a/api/v1/server/handlers/api-tokens/list.go b/api/v1/server/handlers/api-tokens/list.go index 42bb5f171..a6ab0cd72 100644 --- a/api/v1/server/handlers/api-tokens/list.go +++ b/api/v1/server/handlers/api-tokens/list.go @@ -23,7 +23,6 @@ func (a *APITokenService) ApiTokenList(ctx echo.Context, request gen.ApiTokenLis rows[i] = *transformers.ToAPIToken(&tokens[i]) } - // This is the only time the token is sent over the API return gen.ApiTokenList200JSONResponse( gen.ListAPITokensResponse{ Rows: &rows, diff --git a/api/v1/server/handlers/api-tokens/revoke.go b/api/v1/server/handlers/api-tokens/revoke.go index 9190d5e7a..702c81354 100644 --- a/api/v1/server/handlers/api-tokens/revoke.go +++ b/api/v1/server/handlers/api-tokens/revoke.go @@ -15,6 +15,6 @@ func (a *APITokenService) ApiTokenUpdateRevoke(ctx echo.Context, request gen.Api if err != nil { return nil, err } - // This is the only time the token is sent over the API + return gen.ApiTokenUpdateRevoke204Response{}, nil } diff --git a/cmd/hatchet-admin/cli/keyset.go b/cmd/hatchet-admin/cli/keyset.go index 0779ca95a..36b405c5c 100644 --- a/cmd/hatchet-admin/cli/keyset.go +++ b/cmd/hatchet-admin/cli/keyset.go @@ -10,6 +10,7 @@ import ( ) var ( + encryptionKeyDir string cloudKMSCredentialsPath string cloudKMSKeyURI string ) @@ -19,27 +20,14 @@ var keysetCmd = &cobra.Command{ Short: "command for managing keysets.", } -var keysetCreateMasterCmd = &cobra.Command{ - Use: "create-master", - Short: "create a new local master keyset.", +var keysetCreateLocalKeysetsCmd = &cobra.Command{ + Use: "create-local-keys", + Short: "create a new local master keyset and JWT public/private keyset.", Run: func(cmd *cobra.Command, args []string) { - err := runCreateLocalMasterKeyset() + err := runCreateLocalKeysets() if err != nil { - fmt.Printf("Fatal: could not run seed command: %v\n", err) - os.Exit(1) - } - }, -} - -var keysetCreateLocalJWTCmd = &cobra.Command{ - Use: "create-local-jwt", - Short: "create a new local JWT keyset.", - Run: func(cmd *cobra.Command, args []string) { - err := runCreateLocalJWTKeyset() - - if err != nil { - fmt.Printf("Fatal: could not run seed command: %v\n", err) + fmt.Printf("Fatal: could not run [keyset create-local-keys] command: %v\n", err) os.Exit(1) } }, @@ -52,7 +40,7 @@ var keysetCreateCloudKMSJWTCmd = &cobra.Command{ err := runCreateCloudKMSJWTKeyset() if err != nil { - fmt.Printf("Fatal: could not run seed command: %v\n", err) + fmt.Printf("Fatal: could not run [keyset create-cloudkms-jwt] command: %v\n", err) os.Exit(1) } }, @@ -60,10 +48,16 @@ var keysetCreateCloudKMSJWTCmd = &cobra.Command{ func init() { rootCmd.AddCommand(keysetCmd) - keysetCmd.AddCommand(keysetCreateMasterCmd) - keysetCmd.AddCommand(keysetCreateLocalJWTCmd) + keysetCmd.AddCommand(keysetCreateLocalKeysetsCmd) keysetCmd.AddCommand(keysetCreateCloudKMSJWTCmd) + keysetCmd.PersistentFlags().StringVar( + &encryptionKeyDir, + "key-dir", + "", + "if storing keys on disk, path to the directory where encryption keys should be stored", + ) + keysetCreateCloudKMSJWTCmd.PersistentFlags().StringVar( &cloudKMSCredentialsPath, "credentials", @@ -79,31 +73,42 @@ func init() { ) } -func runCreateLocalMasterKeyset() error { - masterKeyBytes, _, _, err := encryption.GenerateLocalKeys() +func runCreateLocalKeysets() error { + masterKeyBytes, privateEc256, publicEc256, err := encryption.GenerateLocalKeys() if err != nil { return err } - fmt.Println("Master Key Bytes:") - fmt.Println(string(masterKeyBytes)) + if encryptionKeyDir != "" { + // we write these as .key files so that they're gitignored by default + err = os.WriteFile(encryptionKeyDir+"/master.key", masterKeyBytes, 0600) - return nil -} + if err != nil { + return err + } -func runCreateLocalJWTKeyset() error { - _, privateEc256, publicEc256, err := encryption.GenerateLocalKeys() + err = os.WriteFile(encryptionKeyDir+"/private_ec256.key", privateEc256, 0600) - if err != nil { - return err - } + if err != nil { + return err + } - fmt.Println("Private EC256 Keyset:") - fmt.Println(string(privateEc256)) + err = os.WriteFile(encryptionKeyDir+"/public_ec256.key", publicEc256, 0600) - fmt.Println("Public EC256 Keyset:") - fmt.Println(string(publicEc256)) + if err != nil { + return err + } + } else { + fmt.Println("Master Key Bytes:") + fmt.Println(string(masterKeyBytes)) + + fmt.Println("Private EC256 Keyset:") + fmt.Println(string(privateEc256)) + + fmt.Println("Public EC256 Keyset:") + fmt.Println(string(publicEc256)) + } return nil } diff --git a/cmd/hatchet-admin/cli/quickstart.go b/cmd/hatchet-admin/cli/quickstart.go index b0e72011e..600f0c6a7 100644 --- a/cmd/hatchet-admin/cli/quickstart.go +++ b/cmd/hatchet-admin/cli/quickstart.go @@ -248,7 +248,6 @@ func generateKeys(generated *generatedConfigFiles) error { } // generate jwt keys - // TODO: clean up this if statement if generated.sc.Encryption.CloudKMS.Enabled && (overwrite || (generated.sc.Encryption.JWT.PublicJWTKeyset == "") || (generated.sc.Encryption.JWT.PrivateJWTKeyset == "")) { privateEc256, publicEc256, err := encryption.GenerateJWTKeysetsFromCloudKMS( generated.sc.Encryption.CloudKMS.KeyURI, diff --git a/cmd/hatchet-admin/cli/token.go b/cmd/hatchet-admin/cli/token.go index 232afa3e4..dc0bd6298 100644 --- a/cmd/hatchet-admin/cli/token.go +++ b/cmd/hatchet-admin/cli/token.go @@ -25,7 +25,7 @@ var tokenCreateAPICmd = &cobra.Command{ err := runCreateAPIToken() if err != nil { - fmt.Printf("Fatal: could not run seed command: %v\n", err) + fmt.Printf("Fatal: could not run [token create] command: %v\n", err) } }, } diff --git a/hack/dev/generate-dev-api-token.sh b/hack/dev/generate-dev-api-token.sh new file mode 100644 index 000000000..e7119c984 --- /dev/null +++ b/hack/dev/generate-dev-api-token.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# This scripts generates a local API token. + +set -eux + +set -a +. .env +set +a + +go run ./cmd/hatchet-admin token create --name "local" --tenant-id 707d0855-80ab-4e1f-a156-f1c4546cbf52 \ No newline at end of file diff --git a/hack/dev/generate-local-encryption-keys.sh b/hack/dev/generate-local-encryption-keys.sh new file mode 100644 index 000000000..74730fbce --- /dev/null +++ b/hack/dev/generate-local-encryption-keys.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# This scripts generates local encryption keys for development. + +set -eux + +ENCRYPTION_KEYS_DIR=./encryption-keys + +# Read CERTS_DIR from args if exists +if [ -n "$1" ]; then + ENCRYPTION_KEYS_DIR=$1 +fi + +mkdir -p $ENCRYPTION_KEYS_DIR + +# Generate a master encryption key +go run ./cmd/hatchet-admin keyset create-local-keys --key-dir $ENCRYPTION_KEYS_DIR + diff --git a/hack/dev/generate-temporal-certs.sh b/hack/dev/generate-x509-certs.sh similarity index 100% rename from hack/dev/generate-temporal-certs.sh rename to hack/dev/generate-x509-certs.sh diff --git a/hack/dev/start-temporal-server.sh b/hack/dev/start-temporal-server.sh deleted file mode 100644 index 0b4400020..000000000 --- a/hack/dev/start-temporal-server.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -eux - -set -a -. .env -set +a - -go run ./cmd/temporal-server diff --git a/hack/dev/write-default-env.sh b/hack/dev/write-default-env.sh deleted file mode 100644 index a9c3c8351..000000000 --- a/hack/dev/write-default-env.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -cat > .env < Date: Fri, 26 Jan 2024 12:15:04 -0800 Subject: [PATCH 8/9] fix: token tests --- internal/auth/token/token_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/auth/token/token_test.go b/internal/auth/token/token_test.go index 2951b10cd..cd78a67b4 100644 --- a/internal/auth/token/token_test.go +++ b/internal/auth/token/token_test.go @@ -129,8 +129,14 @@ func getJWTManager(t *testing.T, conf *database.Config) token.JWTManager { tokenRepo := conf.Repository.APIToken() - return token.NewJWTManager(encryptionService, tokenRepo, &token.TokenOpts{ + jwtManager, err := token.NewJWTManager(encryptionService, tokenRepo, &token.TokenOpts{ Issuer: "hatchet", Audience: "hatchet", }) + + if err != nil { + t.Fatal(err.Error()) + } + + return jwtManager } From ed4a45cf268416cfeb6d935d232f09bce0bed2bd Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Fri, 26 Jan 2024 12:27:09 -0800 Subject: [PATCH 9/9] chore: address review comments and fix tests --- .../tenant-settings/components/revoke-token-form.tsx | 2 ++ internal/repository/prisma/dbsqlc/models.go | 8 ++++---- internal/repository/prisma/dbsqlc/schema.sql | 10 +++++----- python-client/hatchet_sdk/dispatcher_pb2_grpc.py | 2 -- python-client/hatchet_sdk/events_pb2_grpc.py | 2 -- python-client/hatchet_sdk/workflows_pb2_grpc.py | 2 -- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/frontend/app/src/pages/main/tenant-settings/components/revoke-token-form.tsx b/frontend/app/src/pages/main/tenant-settings/components/revoke-token-form.tsx index ced7e48ed..c15cb0f52 100644 --- a/frontend/app/src/pages/main/tenant-settings/components/revoke-token-form.tsx +++ b/frontend/app/src/pages/main/tenant-settings/components/revoke-token-form.tsx @@ -24,6 +24,8 @@ export function RevokeTokenForm({ className, ...props }: RevokeTokenFormProps) {
Are you sure you want to revoke the API token {props.apiToken.name}? + This action will immediately prevent any services running with this + token from dispatching events or executing steps.