From e4ff42968df5075ac074da8d6ab27b0750b762dc Mon Sep 17 00:00:00 2001 From: Eugene Zagidullin Date: Sat, 26 Oct 2024 01:44:41 +0300 Subject: [PATCH 1/2] DynamoDB Watermark Backend (#499) * migrate to aws-sdk-go-v2 * mod tidy * watermark moved to subpackage * oops * AWS watermark backend * configurable watermark backend --------- Co-authored-by: GImbrailo --- cmd/approve-list-svc/server/server_test.go | 3 +- cmd/commands/root.go | 11 +- docs/aws_kms.md | 5 +- docs/start.md | 21 +- go.mod | 19 +- go.sum | 38 +++- pkg/config/config.go | 10 + pkg/signatory/policy_hook_test.go | 5 +- pkg/signatory/signatory.go | 5 +- pkg/signatory/signatory_test.go | 7 +- pkg/signatory/watermark.go | 21 -- pkg/signatory/watermark/aws.go | 204 ++++++++++++++++++ .../{watermark_file.go => watermark/file.go} | 22 +- .../{watermark_mem.go => watermark/mem.go} | 19 +- .../migration.go} | 2 +- pkg/signatory/watermark/watermark.go | 53 +++++ .../{ => watermark}/watermark_test.go | 9 +- pkg/vault/aws/awskms.go | 72 ++++--- signatory.yaml | 5 +- test/auth_test.go | 3 +- 20 files changed, 437 insertions(+), 97 deletions(-) delete mode 100644 pkg/signatory/watermark.go create mode 100644 pkg/signatory/watermark/aws.go rename pkg/signatory/{watermark_file.go => watermark/file.go} (88%) rename pkg/signatory/{watermark_mem.go => watermark/mem.go} (64%) rename pkg/signatory/{watermark_migration.go => watermark/migration.go} (99%) create mode 100644 pkg/signatory/watermark/watermark.go rename pkg/signatory/{ => watermark}/watermark_test.go (92%) diff --git a/cmd/approve-list-svc/server/server_test.go b/cmd/approve-list-svc/server/server_test.go index 5c0b8726..46938b65 100644 --- a/cmd/approve-list-svc/server/server_test.go +++ b/cmd/approve-list-svc/server/server_test.go @@ -15,6 +15,7 @@ import ( "github.com/ecadlabs/signatory/pkg/config" "github.com/ecadlabs/signatory/pkg/hashmap" "github.com/ecadlabs/signatory/pkg/signatory" + "github.com/ecadlabs/signatory/pkg/signatory/watermark" "github.com/ecadlabs/signatory/pkg/vault" "github.com/ecadlabs/signatory/pkg/vault/memory" "github.com/stretchr/testify/require" @@ -57,7 +58,7 @@ func testServer(t *testing.T, addr []net.IP) error { conf := signatory.Config{ Vaults: map[string]*config.VaultConfig{"mock": {Driver: "mock"}}, - Watermark: signatory.IgnoreWatermark{}, + Watermark: watermark.Ignore{}, VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { return memory.New([]*memory.PrivateKey{ { diff --git a/cmd/commands/root.go b/cmd/commands/root.go index fca72b00..f667c1f5 100644 --- a/cmd/commands/root.go +++ b/cmd/commands/root.go @@ -9,6 +9,7 @@ import ( "github.com/ecadlabs/signatory/pkg/config" "github.com/ecadlabs/signatory/pkg/metrics" "github.com/ecadlabs/signatory/pkg/signatory" + "github.com/ecadlabs/signatory/pkg/signatory/watermark" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -49,11 +50,11 @@ func NewRootCommand(c *Context, name string) *cobra.Command { } } - if baseDir == "" { - baseDir = conf.BaseDir + if baseDir != "" { + conf.BaseDir = baseDir } - baseDir = os.ExpandEnv(baseDir) - if err := os.MkdirAll(baseDir, 0770); err != nil { + conf.BaseDir = os.ExpandEnv(conf.BaseDir) + if err := os.MkdirAll(conf.BaseDir, 0770); err != nil { return err } @@ -78,7 +79,7 @@ func NewRootCommand(c *Context, name string) *cobra.Command { return err } - watermark, err := signatory.NewFileWatermark(baseDir) + watermark, err := watermark.Registry().New(cmd.Context(), conf.Watermark.Driver, &conf.Watermark.Config, conf) if err != nil { return err } diff --git a/docs/aws_kms.md b/docs/aws_kms.md index fdf934a8..ddb1dedb 100644 --- a/docs/aws_kms.md +++ b/docs/aws_kms.md @@ -19,7 +19,6 @@ vaults: aws: driver: awskms config: - user_name: access_key_id: secret_access_key: region: @@ -29,7 +28,6 @@ vaults: Name | Type | Required | Description -----|------|:--------:|------------ -user_name | string |✅| IAM user name access_key_id | string | OPTIONAL | IAM user detail secret_access_key | string | OPTIONAL | IAM user detail region | string | ✅ | Region where key is created @@ -54,7 +52,7 @@ To generate a new private key withing AWS, you must: ## Example Configuration for the AWS KMS vault in Signatory -This example shows a Signatory vault configuration for AWS KMS. Text in `{}` must be replaced, for example, `{AWS_User_Name}` should be replaced with your AWS username. +This example shows a Signatory vault configuration for AWS KMS. Text in `{}` must be replaced. ``` @@ -63,7 +61,6 @@ vaults: awskms: driver: awskms config: - user_name: {AWS_User_Name} access_key_id: {Access_Key_ID_In_AWS_User_Profile} secret_access_key: {Secret_access_Key_ID_In_AWS_User_Profile} region: {AWS_Region} diff --git a/docs/start.md b/docs/start.md index 4f55bc19..b4abf938 100644 --- a/docs/start.md +++ b/docs/start.md @@ -74,6 +74,10 @@ vaults: config: file: /etc/signatory/secret.json +watermark: + # Default + driver: file + # List enabled public keys hashes here tezos: # Default policy allows "block" and "endorsement" operations @@ -111,7 +115,7 @@ tezos: ] ``` -## Configuration Example - AWS KMS Vault +### Configuration Example - AWS KMS Vault This configuration example uses AWS KMS as ```yaml @@ -128,7 +132,6 @@ vaults: aws: driver: awskms config: - user_name: signatory_testnets # IAM User or Role access_key_id: # Optional secret_access_key: # Optional region: us-west-2 @@ -145,6 +148,20 @@ tezos: - transaction ``` +### Watermark backend + +Basic syntax: + +```yaml +# Optional +watermark: + driver: + # Optional + config: +``` + +Currently three backends are supported: `file` (a default one), `mem` (for testing purpose only) and `aws`. See [AWS KMS][aws] for configuration syntax. + ## Backends * [AWS KMS](aws_kms.md) * [Azure Key Vault](azure_kms.md) diff --git a/go.mod b/go.mod index a45c1f64..5c0ef366 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,12 @@ toolchain go1.23.1 require ( cloud.google.com/go/kms v1.15.5 + github.com/aws/aws-sdk-go-v2 v1.30.3 + github.com/aws/aws-sdk-go-v2/config v1.27.27 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.10 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.4 + github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 + github.com/aws/smithy-go v1.20.3 github.com/certusone/yubihsm-go v0.3.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/ecadlabs/goblst v1.0.0 @@ -33,6 +39,18 @@ require ( cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/ecadlabs/pretty v0.0.0-20230412124801-f948fc689a04 // indirect @@ -64,7 +82,6 @@ require ( ) require ( - github.com/aws/aws-sdk-go v1.48.11 github.com/beorn7/perks v1.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815 // indirect diff --git a/go.sum b/go.sum index 154e0d01..f78daf3c 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,42 @@ cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.48.11 h1:9YbiSbaF/jWi+qLRl+J5dEhr2mcbDYHmKg2V7RBcD5M= -github.com/aws/aws-sdk-go v1.48.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.10 h1:orAIBscNu5aIjDOnKIrjO+IUFPMLKj3Lp0bPf4chiPc= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.10/go.mod h1:GNjJ8daGhv10hmQYCnmkV8HuY6xXOXV4vzBssSjEIlU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.4 h1:utG3S4T+X7nONPIpRoi1tVcQdAdJxntiVS2yolPJyXc= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.4/go.mod h1:q9vzW3Xr1KEXa8n4waHiFt1PrppNDlMymlYP+xpsFbY= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.3 h1:r27/FnxLPixKBRIlslsvhqscBuMK8uysCYG9Kfgm098= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.3/go.mod h1:jqOFyN+QSWSoQC+ppyc4weiO8iNQXbzRbxDjQ1ayYd4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.16 h1:lhAX5f7KpgwyieXjbDnRTjPEUI0l3emSRyxXj1PXP8w= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.16/go.mod h1:AblAlCwvi7Q/SFowvckgN+8M3uFPlopSYeLlbNDArhA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= diff --git a/pkg/config/config.go b/pkg/config/config.go index 4ab1ed2b..a0d38006 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -51,6 +51,13 @@ type Config struct { Server ServerConfig `yaml:"server"` PolicyHook *PolicyHook `yaml:"policy_hook"` BaseDir string `yaml:"base_dir" validate:"required"` + Watermark *WatermarkConfig `yaml:"watermark"` +} + +// WatermarkConfig represents watermark backend configuration +type WatermarkConfig struct { + Driver string `yaml:"driver" validate:"required"` + Config yaml.Node `yaml:"config"` } var defaultConfig = Config{ @@ -59,6 +66,9 @@ var defaultConfig = Config{ UtilityAddress: ":9583", }, BaseDir: "/var/lib/signatory", + Watermark: &WatermarkConfig{ + Driver: "file", + }, } // Read read the config from a file diff --git a/pkg/signatory/policy_hook_test.go b/pkg/signatory/policy_hook_test.go index 41a91cf9..df7aacd8 100644 --- a/pkg/signatory/policy_hook_test.go +++ b/pkg/signatory/policy_hook_test.go @@ -17,6 +17,7 @@ import ( "github.com/ecadlabs/signatory/pkg/config" "github.com/ecadlabs/signatory/pkg/hashmap" "github.com/ecadlabs/signatory/pkg/signatory" + "github.com/ecadlabs/signatory/pkg/signatory/watermark" "github.com/ecadlabs/signatory/pkg/vault" "github.com/ecadlabs/signatory/pkg/vault/memory" "github.com/stretchr/testify/require" @@ -99,7 +100,7 @@ func testPolicyHookAuth(t *testing.T, status int) error { conf := signatory.Config{ Vaults: map[string]*config.VaultConfig{"mock": {Driver: "mock"}}, - Watermark: signatory.IgnoreWatermark{}, + Watermark: watermark.Ignore{}, VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { return memory.New([]*memory.PrivateKey{ { @@ -138,7 +139,7 @@ func testPolicyHook(t *testing.T, status int) error { conf := signatory.Config{ Vaults: map[string]*config.VaultConfig{"mock": {Driver: "mock"}}, - Watermark: signatory.IgnoreWatermark{}, + Watermark: watermark.Ignore{}, VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { return memory.New([]*memory.PrivateKey{ { diff --git a/pkg/signatory/signatory.go b/pkg/signatory/signatory.go index d28c263c..e5abebac 100644 --- a/pkg/signatory/signatory.go +++ b/pkg/signatory/signatory.go @@ -25,6 +25,7 @@ import ( "github.com/ecadlabs/signatory/pkg/errors" "github.com/ecadlabs/signatory/pkg/hashmap" "github.com/ecadlabs/signatory/pkg/signatory/request" + "github.com/ecadlabs/signatory/pkg/signatory/watermark" "github.com/ecadlabs/signatory/pkg/vault" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" @@ -393,7 +394,7 @@ func (s *Signatory) Sign(ctx context.Context, req *SignRequest) (crypt.Signature l.WithField(logRaw, hex.EncodeToString(req.Message)).Log(level, "About to sign raw bytes") digest := crypt.DigestFunc(req.Message) signFunc := func(ctx context.Context, message []byte, key vault.StoredKey) (crypt.Signature, error) { - if err = s.config.Watermark.IsSafeToSign(req.PublicKeyHash, msg, &digest); err != nil { + if err = s.config.Watermark.IsSafeToSign(ctx, req.PublicKeyHash, msg, &digest); err != nil { err = errors.Wrap(err, http.StatusConflict) l.Error(err) return nil, err @@ -544,7 +545,7 @@ type Config struct { Policy Policy Vaults map[string]*config.VaultConfig Interceptor SignInterceptor - Watermark Watermark + Watermark watermark.Watermark Logger log.FieldLogger VaultFactory vault.Factory PolicyHook *PolicyHook diff --git a/pkg/signatory/signatory_test.go b/pkg/signatory/signatory_test.go index 68494e5e..f848c7ca 100644 --- a/pkg/signatory/signatory_test.go +++ b/pkg/signatory/signatory_test.go @@ -12,6 +12,7 @@ import ( "github.com/ecadlabs/signatory/pkg/config" "github.com/ecadlabs/signatory/pkg/hashmap" "github.com/ecadlabs/signatory/pkg/signatory" + "github.com/ecadlabs/signatory/pkg/signatory/watermark" "github.com/ecadlabs/signatory/pkg/vault" "github.com/ecadlabs/signatory/pkg/vault/memory" "github.com/stretchr/testify/require" @@ -23,7 +24,7 @@ const privateKey = "edsk4FTF78Qf1m2rykGpHqostAiq5gYW4YZEoGUSWBTJr2njsDHSnd" func TestImport(t *testing.T) { conf := signatory.Config{ Vaults: map[string]*config.VaultConfig{"mock": {Driver: "mock"}}, - Watermark: signatory.IgnoreWatermark{}, + Watermark: watermark.Ignore{}, VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { v, err := memory.New(nil, "Mock") if err != nil { @@ -310,7 +311,7 @@ func TestPolicy(t *testing.T) { t.Run(c.title, func(t *testing.T) { conf := signatory.Config{ Vaults: map[string]*config.VaultConfig{"mock": {Driver: "mock"}}, - Watermark: signatory.IgnoreWatermark{}, + Watermark: watermark.Ignore{}, VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { return memory.NewUnparsed([]*memory.UnparsedKey{{Data: privateKey}}, "Mock"), nil }), @@ -400,7 +401,7 @@ func TestListPublicKeys(t *testing.T) { t.Run(c.title, func(t *testing.T) { conf := signatory.Config{ Vaults: map[string]*config.VaultConfig{"test": {Driver: "test"}}, - Watermark: signatory.IgnoreWatermark{}, + Watermark: watermark.Ignore{}, VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { return NewTestVault(nil, c.lpk, nil, nil, "test"), nil }), diff --git a/pkg/signatory/watermark.go b/pkg/signatory/watermark.go deleted file mode 100644 index 043ab247..00000000 --- a/pkg/signatory/watermark.go +++ /dev/null @@ -1,21 +0,0 @@ -package signatory - -import ( - "github.com/ecadlabs/gotez/v2/crypt" - "github.com/ecadlabs/gotez/v2/protocol" -) - -// Watermark tests level against stored high watermark -type Watermark interface { - IsSafeToSign(pkh crypt.PublicKeyHash, req protocol.SignRequest, digest *crypt.Digest) error -} - -// IgnoreWatermark watermark that do not validation and return true -type IgnoreWatermark struct{} - -// IsSafeToSign always return true -func (w IgnoreWatermark) IsSafeToSign(crypt.PublicKeyHash, protocol.SignRequest, *crypt.Digest) error { - return nil -} - -var _ Watermark = (*IgnoreWatermark)(nil) diff --git a/pkg/signatory/watermark/aws.go b/pkg/signatory/watermark/aws.go new file mode 100644 index 00000000..4cdd925f --- /dev/null +++ b/pkg/signatory/watermark/aws.go @@ -0,0 +1,204 @@ +package watermark + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/aws/smithy-go" + tz "github.com/ecadlabs/gotez/v2" + "github.com/ecadlabs/gotez/v2/crypt" + "github.com/ecadlabs/gotez/v2/protocol" + "github.com/ecadlabs/signatory/pkg/config" + "github.com/ecadlabs/signatory/pkg/signatory/request" + awskms "github.com/ecadlabs/signatory/pkg/vault/aws" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" +) + +const ( + readCapacityUnits = 5 + writeCapacityUnits = 5 + defaultTable = "watermark" +) + +type AWSConfig struct { + awskms.Config + Table string `yaml:"table"` +} + +func (c *AWSConfig) table() string { + if c.Table != "" { + return c.Table + } + return defaultTable +} + +type AWS struct { + cfg AWSConfig + client *dynamodb.Client +} + +func NewAWSWatermark(ctx context.Context, config *AWSConfig) (*AWS, error) { + cfg, err := awskms.NewConfig(ctx, &config.Config) + if err != nil { + return nil, err + } + + client := dynamodb.NewFromConfig(cfg) + a := AWS{ + client: client, + cfg: *config, + } + if err := a.maybeCreateTable(ctx); err != nil { + return nil, fmt.Errorf("(AWSWatermark) NewAWSWatermark: %w", err) + } + return &a, nil +} + +func (a *AWS) maybeCreateTable(ctx context.Context) error { + _, err := a.client.CreateTable(ctx, &dynamodb.CreateTableInput{ + AttributeDefinitions: []types.AttributeDefinition{ + { + AttributeName: aws.String("idx"), + AttributeType: types.ScalarAttributeTypeS, + }, + { + AttributeName: aws.String("request"), + AttributeType: types.ScalarAttributeTypeS, + }, + }, + KeySchema: []types.KeySchemaElement{ + { + AttributeName: aws.String("idx"), + KeyType: types.KeyTypeHash, + }, + { + AttributeName: aws.String("request"), + KeyType: types.KeyTypeRange, + }, + }, + ProvisionedThroughput: &types.ProvisionedThroughput{ + ReadCapacityUnits: aws.Int64(readCapacityUnits), + WriteCapacityUnits: aws.Int64(writeCapacityUnits), + }, + TableName: aws.String(a.cfg.table()), + }) + if err != nil { + var serr smithy.APIError + if errors.As(err, &serr) && serr.ErrorCode() == "ResourceInUseException" { + return nil + } + return err + } + log.WithField("table", a.cfg.table()).Info("table created") + waiter := dynamodb.NewTableExistsWaiter(a.client) + return waiter.Wait(context.TODO(), &dynamodb.DescribeTableInput{ + TableName: aws.String(a.cfg.table()), + }, time.Minute*5) // give excess time +} + +type watermark struct { + Idx string `dynamodbav:"idx"` + Request string `dynamodbav:"request"` + Level int32 `dynamodbav:"lvl"` + Round int32 `dynamodbav:"round"` + Digest *tz.BlockPayloadHash `dynamodbav:"digest"` +} + +func (w *watermark) key() map[string]types.AttributeValue { + return map[string]types.AttributeValue{ + "idx": &types.AttributeValueMemberS{Value: w.Idx}, + "request": &types.AttributeValueMemberS{Value: w.Request}, + } +} + +func (w *watermark) watermark() *request.Watermark { + return &request.Watermark{ + Level: w.Level, + Round: w.Round, + Hash: tz.Some(*w.Digest), + } +} + +func (a *AWS) IsSafeToSign(ctx context.Context, pkh crypt.PublicKeyHash, req protocol.SignRequest, digest *crypt.Digest) error { + m, ok := req.(request.WithWatermark) + if !ok { + // watermark is not required + return nil + } + wm := request.NewWatermark(m, digest) + prev := watermark{ + Idx: strings.Join([]string{m.GetChainID().String(), pkh.String()}, "/"), + Request: req.SignRequestKind(), + } + for { + response, err := a.client.GetItem(ctx, &dynamodb.GetItemInput{ + Key: prev.key(), + TableName: aws.String(a.cfg.table()), + }) + if err != nil { + return fmt.Errorf("(AWSWatermark) IsSafeToSign: %w", err) + } + + update := watermark{ + Idx: prev.Idx, + Request: prev.Request, + Level: wm.Level, + Round: wm.Round, + Digest: wm.Hash.UnwrapPtr(), + } + item, err := attributevalue.MarshalMap(&update) + if err != nil { + return fmt.Errorf("(AWSWatermark) IsSafeToSign: %w", err) + } + input := dynamodb.PutItemInput{ + TableName: aws.String(a.cfg.table()), + Item: item, + } + + if response.Item != nil { + if err := attributevalue.UnmarshalMap(response.Item, &prev); err != nil { + return fmt.Errorf("(AWSWatermark) IsSafeToSign: %w", err) + } + if !wm.Validate(prev.watermark()) { + return ErrWatermark + } + input.ConditionExpression = aws.String("lvl = :lvl AND round = :round AND digest = :digest") + input.ExpressionAttributeValues = map[string]types.AttributeValue{ + ":lvl": response.Item["lvl"], + ":round": response.Item["round"], + ":digest": response.Item["digest"], + } + } else { + input.ConditionExpression = aws.String("attribute_not_exists(idx)") + } + + _, err = a.client.PutItem((ctx), &input) + var serr smithy.APIError + if err == nil { + return nil + } else if !errors.As(err, &serr) || serr.ErrorCode() != "ConditionalCheckFailedException" { + return fmt.Errorf("(AWSWatermark) IsSafeToSign: %w", err) + } + // retry + } +} + +func init() { + RegisterWatermark("aws", func(ctx context.Context, node *yaml.Node, global *config.Config) (Watermark, error) { + var conf AWSConfig + if node != nil { + if err := node.Decode(&conf); err != nil { + return nil, err + } + } + return NewAWSWatermark(ctx, &conf) + }) +} diff --git a/pkg/signatory/watermark_file.go b/pkg/signatory/watermark/file.go similarity index 88% rename from pkg/signatory/watermark_file.go rename to pkg/signatory/watermark/file.go index 0a8def77..b3715ebf 100644 --- a/pkg/signatory/watermark_file.go +++ b/pkg/signatory/watermark/file.go @@ -1,7 +1,8 @@ -package signatory +package watermark import ( "bufio" + "context" "encoding/json" "errors" "fmt" @@ -14,14 +15,16 @@ import ( "github.com/ecadlabs/gotez/v2/b58" "github.com/ecadlabs/gotez/v2/crypt" "github.com/ecadlabs/gotez/v2/protocol" + "github.com/ecadlabs/signatory/pkg/config" "github.com/ecadlabs/signatory/pkg/hashmap" "github.com/ecadlabs/signatory/pkg/signatory/request" log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" ) -type FileWatermark struct { +type File struct { baseDir string - mem InMemoryWatermark + mem InMemory } // chain -> delegate(pkh) -> request type -> watermark @@ -69,8 +72,9 @@ func tryLoad(baseDir string) (map[tz.ChainID]delegateMap, error) { return out, nil } -func NewFileWatermark(baseDir string) (*FileWatermark, error) { - wm := FileWatermark{ +func NewFileWatermark(baseDir string) (*File, error) { + + wm := File{ baseDir: baseDir, } var err error @@ -147,7 +151,7 @@ func writeWatermarkData(baseDir string, data delegateMap, chain *tz.ChainID) err return w.Flush() } -func (f *FileWatermark) IsSafeToSign(pkh crypt.PublicKeyHash, req protocol.SignRequest, digest *crypt.Digest) error { +func (f *File) IsSafeToSign(ctx context.Context, pkh crypt.PublicKeyHash, req protocol.SignRequest, digest *crypt.Digest) error { m, ok := req.(request.WithWatermark) if !ok { // watermark is not required @@ -163,4 +167,8 @@ func (f *FileWatermark) IsSafeToSign(pkh crypt.PublicKeyHash, req protocol.SignR return writeWatermarkData(f.baseDir, f.mem.chains[*chain], chain) } -var _ Watermark = (*FileWatermark)(nil) +func init() { + RegisterWatermark("file", func(ctx context.Context, node *yaml.Node, global *config.Config) (Watermark, error) { + return NewFileWatermark(global.BaseDir) + }) +} diff --git a/pkg/signatory/watermark_mem.go b/pkg/signatory/watermark/mem.go similarity index 64% rename from pkg/signatory/watermark_mem.go rename to pkg/signatory/watermark/mem.go index 8fa59187..dfd08ae4 100644 --- a/pkg/signatory/watermark_mem.go +++ b/pkg/signatory/watermark/mem.go @@ -1,28 +1,31 @@ -package signatory +package watermark import ( + "context" "sync" tz "github.com/ecadlabs/gotez/v2" "github.com/ecadlabs/gotez/v2/crypt" "github.com/ecadlabs/gotez/v2/protocol" + "github.com/ecadlabs/signatory/pkg/config" "github.com/ecadlabs/signatory/pkg/signatory/request" + "gopkg.in/yaml.v3" ) -// InMemoryWatermark keep previous operation in memory -type InMemoryWatermark struct { +// InMemory keep previous operation in memory +type InMemory struct { chains map[tz.ChainID]delegateMap mtx sync.Mutex } // IsSafeToSign return true if this msgID is safe to sign -func (w *InMemoryWatermark) IsSafeToSign(pkh crypt.PublicKeyHash, req protocol.SignRequest, digest *crypt.Digest) error { +func (w *InMemory) IsSafeToSign(ctx context.Context, pkh crypt.PublicKeyHash, req protocol.SignRequest, digest *crypt.Digest) error { w.mtx.Lock() defer w.mtx.Unlock() return w.isSafeToSignUnlocked(pkh, req, digest) } -func (w *InMemoryWatermark) isSafeToSignUnlocked(pkh crypt.PublicKeyHash, req protocol.SignRequest, digest *crypt.Digest) error { +func (w *InMemory) isSafeToSignUnlocked(pkh crypt.PublicKeyHash, req protocol.SignRequest, digest *crypt.Digest) error { m, ok := req.(request.WithWatermark) if !ok { // watermark is not required @@ -55,4 +58,8 @@ func (w *InMemoryWatermark) isSafeToSignUnlocked(pkh crypt.PublicKeyHash, req pr return nil } -var _ Watermark = (*InMemoryWatermark)(nil) +func init() { + RegisterWatermark("mem", func(context.Context, *yaml.Node, *config.Config) (Watermark, error) { + return new(InMemory), nil + }) +} diff --git a/pkg/signatory/watermark_migration.go b/pkg/signatory/watermark/migration.go similarity index 99% rename from pkg/signatory/watermark_migration.go rename to pkg/signatory/watermark/migration.go index 7d7329f1..329914ab 100644 --- a/pkg/signatory/watermark_migration.go +++ b/pkg/signatory/watermark/migration.go @@ -1,4 +1,4 @@ -package signatory +package watermark import ( "encoding/json" diff --git a/pkg/signatory/watermark/watermark.go b/pkg/signatory/watermark/watermark.go new file mode 100644 index 00000000..3055c065 --- /dev/null +++ b/pkg/signatory/watermark/watermark.go @@ -0,0 +1,53 @@ +package watermark + +import ( + "context" + "fmt" + + "github.com/ecadlabs/gotez/v2/crypt" + "github.com/ecadlabs/gotez/v2/protocol" + "github.com/ecadlabs/signatory/pkg/config" + log "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" +) + +// Watermark tests level against stored high watermark +type Watermark interface { + IsSafeToSign(ctx context.Context, pkh crypt.PublicKeyHash, req protocol.SignRequest, digest *crypt.Digest) error +} + +// Ignore watermark that do not validation and return true +type Ignore struct{} + +// IsSafeToSign always return true +func (w Ignore) IsSafeToSign(context.Context, crypt.PublicKeyHash, protocol.SignRequest, *crypt.Digest) error { + return nil +} + +var _ Watermark = (*Ignore)(nil) + +type Factory interface { + New(ctx context.Context, name string, conf *yaml.Node, global *config.Config) (Watermark, error) +} + +type newWMBackendFunc func(ctx context.Context, conf *yaml.Node, global *config.Config) (Watermark, error) + +type registry map[string]newWMBackendFunc + +func (r registry) New(ctx context.Context, name string, conf *yaml.Node, global *config.Config) (Watermark, error) { + if newFunc, ok := r[name]; ok { + log.WithField("backend", name).Info("Initializing watermark backend") + return newFunc(ctx, conf, global) + } + return nil, fmt.Errorf("unknown watermark backend: %s", name) +} + +var wmRegistry = make(registry) + +func RegisterWatermark(name string, newFunc newWMBackendFunc) { + wmRegistry[name] = newFunc +} + +func Registry() Factory { + return wmRegistry +} diff --git a/pkg/signatory/watermark_test.go b/pkg/signatory/watermark/watermark_test.go similarity index 92% rename from pkg/signatory/watermark_test.go rename to pkg/signatory/watermark/watermark_test.go index e83a6475..a989897d 100644 --- a/pkg/signatory/watermark_test.go +++ b/pkg/signatory/watermark/watermark_test.go @@ -1,8 +1,9 @@ //go:build !integration -package signatory +package watermark import ( + "context" "fmt" "os" "testing" @@ -100,10 +101,10 @@ func TestWatermark(t *testing.T) { } t.Run("memory", func(t *testing.T) { - var wm InMemoryWatermark + var wm InMemory for i, c := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - err := wm.IsSafeToSign(c.pkh, c.req, &c.reqDigest) + err := wm.IsSafeToSign(context.Background(), c.pkh, c.req, &c.reqDigest) if c.expectErr { assert.Error(t, err) } else { @@ -120,7 +121,7 @@ func TestWatermark(t *testing.T) { require.NoError(t, err) for i, c := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - err := wm.IsSafeToSign(c.pkh, c.req, &c.reqDigest) + err := wm.IsSafeToSign(context.Background(), c.pkh, c.req, &c.reqDigest) if c.expectErr { assert.Error(t, err) } else { diff --git a/pkg/vault/aws/awskms.go b/pkg/vault/aws/awskms.go index f7dae4ee..7df844bd 100644 --- a/pkg/vault/aws/awskms.go +++ b/pkg/vault/aws/awskms.go @@ -6,12 +6,12 @@ import ( "fmt" "os" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/kms" + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/aws/smithy-go" "github.com/ecadlabs/gotez/v2/crypt" - "github.com/ecadlabs/signatory/pkg/config" "github.com/ecadlabs/signatory/pkg/cryptoutils" "github.com/ecadlabs/signatory/pkg/vault" @@ -20,14 +20,13 @@ import ( // Config contains AWS KMS backend configuration type Config struct { - UserName string `yaml:"user_name" validate:"required"` AccessKeyID string `yaml:"access_key_id"` AccessKey string `yaml:"secret_access_key"` - Region string `yaml:"region" validate:"required"` + Region string `yaml:"region"` } type Vault struct { - kmsapi *kms.KMS + client *kms.Client config Config } @@ -40,7 +39,7 @@ type awsKMSKey struct { type awsKMSIterator struct { ctx context.Context v *Vault - kmsapi *kms.KMS + client *kms.Client lko *kms.ListKeysOutput index int } @@ -56,14 +55,14 @@ func (c *awsKMSKey) ID() string { } func (v *Vault) GetPublicKey(ctx context.Context, keyID string) (vault.StoredKey, error) { - pkresp, err := v.kmsapi.GetPublicKeyWithContext(ctx, &kms.GetPublicKeyInput{ + pkresp, err := v.client.GetPublicKey(ctx, &kms.GetPublicKeyInput{ KeyId: &keyID, }) if err != nil { return nil, err } - if *pkresp.KeyUsage != kms.KeyUsageTypeSignVerify { + if pkresp.KeyUsage != types.KeyUsageTypeSignVerify { return nil, errors.New("key usage must be SIGN_VERIFY") } @@ -97,7 +96,8 @@ func (i *awsKMSIterator) Next() (key vault.StoredKey, err error) { Marker: i.lko.NextMarker, } } // otherwise leave it nil - i.lko, err = i.kmsapi.ListKeys(lkin) + + i.lko, err = i.client.ListKeys(i.ctx, lkin) if err != nil { return nil, err } @@ -106,10 +106,11 @@ func (i *awsKMSIterator) Next() (key vault.StoredKey, err error) { key, err = i.v.GetPublicKey(i.ctx, *i.lko.Keys[i.index].KeyId) i.index += 1 + var kmserr smithy.APIError if err == nil { return key, nil - } else if awsErr, ok := err.(awserr.Error); ok { - if awsErr.Code() != "AccessDeniedException" { + } else if errors.As(err, &kmserr) { + if kmserr.ErrorCode() != "AccessDeniedException" { return nil, err } } else if err != crypt.ErrUnsupportedKeyType { @@ -123,7 +124,7 @@ func (v *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { return &awsKMSIterator{ ctx: ctx, v: v, - kmsapi: v.kmsapi, + client: v.client, } } @@ -135,11 +136,11 @@ func (v *Vault) Name() string { func (v *Vault) SignMessage(ctx context.Context, message []byte, key vault.StoredKey) (crypt.Signature, error) { digest := crypt.DigestFunc(message) kid := key.ID() - sout, err := v.kmsapi.Sign(&kms.SignInput{ + sout, err := v.client.Sign(ctx, &kms.SignInput{ KeyId: &kid, Message: digest[:], - MessageType: aws.String(kms.MessageTypeDigest), - SigningAlgorithm: aws.String(kms.SigningAlgorithmSpecEcdsaSha256), + MessageType: types.MessageTypeDigest, + SigningAlgorithm: types.SigningAlgorithmSpecEcdsaSha256, }) if err != nil { return nil, err @@ -153,18 +154,27 @@ func (v *Vault) SignMessage(ctx context.Context, message []byte, key vault.Store return sig, nil } -// New creates new AWS KMS backend -func New(ctx context.Context, config *Config) (*Vault, error) { +func NewConfig(ctx context.Context, config *Config) (aws.Config, error) { if config.AccessKeyID != "" { os.Setenv("AWS_ACCESS_KEY_ID", config.AccessKeyID) os.Setenv("AWS_SECRET_ACCESS_KEY", config.AccessKey) } - os.Setenv("AWS_REGION", config.Region) - sess := session.Must(session.NewSession()) + if config.Region != "" { + os.Setenv("AWS_REGION", config.Region) + } + return awsconfig.LoadDefaultConfig(ctx) +} - api := kms.New(sess) +// New creates new AWS KMS backend +func New(ctx context.Context, config *Config) (*Vault, error) { + cfg, err := NewConfig(ctx, config) + if err != nil { + return nil, err + } + + client := kms.NewFromConfig(cfg) return &Vault{ - kmsapi: api, + client: client, config: *config, }, nil } @@ -172,17 +182,11 @@ func New(ctx context.Context, config *Config) (*Vault, error) { func init() { vault.RegisterVault("awskms", func(ctx context.Context, node *yaml.Node) (vault.Vault, error) { var conf Config - if node == nil || node.Kind == 0 { - return nil, errors.New("(AWSKMS): config is missing") - } - if err := node.Decode(&conf); err != nil { - return nil, err - } - - if err := config.Validator().Struct(&conf); err != nil { - return nil, err + if node != nil { + if err := node.Decode(&conf); err != nil { + return nil, err + } } - return New(ctx, &conf) }) } diff --git a/signatory.yaml b/signatory.yaml index 4a9e1274..02ba5b4e 100644 --- a/signatory.yaml +++ b/signatory.yaml @@ -15,7 +15,6 @@ server: secret: secret2 jwt_exp: 30 - vaults: # Name is used to identify backend during import process kms: @@ -49,6 +48,10 @@ vaults: transitConfig: mountPoint: "transit/" +watermark: + # Default + driver: file + # List enabled public keys hashes here tezos: # This example does not specifiy a policy, and be default will allow signing of "block" and "endorsement" operations only. diff --git a/test/auth_test.go b/test/auth_test.go index 7295ee45..3a00d961 100644 --- a/test/auth_test.go +++ b/test/auth_test.go @@ -17,6 +17,7 @@ import ( "github.com/ecadlabs/signatory/pkg/hashmap" "github.com/ecadlabs/signatory/pkg/server" "github.com/ecadlabs/signatory/pkg/signatory" + "github.com/ecadlabs/signatory/pkg/signatory/watermark" "github.com/ecadlabs/signatory/pkg/vault" "github.com/ecadlabs/signatory/pkg/vault/memory" "github.com/stretchr/testify/require" @@ -73,7 +74,7 @@ func TestAuthenticatedRequest(t *testing.T) { conf := signatory.Config{ Vaults: map[string]*config.VaultConfig{"mock": {Driver: "mock"}}, - Watermark: signatory.IgnoreWatermark{}, + Watermark: watermark.Ignore{}, VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { return memory.New([]*memory.PrivateKey{{PrivateKey: signPriv}}, "Mock") }), From cb93f83e5dad3d8e5e177ff8f9deb0bae970181e Mon Sep 17 00:00:00 2001 From: Eugene Zagidullin Date: Sat, 26 Oct 2024 02:29:36 +0300 Subject: [PATCH 2/2] Pseudo ops (#503) * gotez version bump * gotez v2.0.6 * CLI test updated * handle pseudo-ops * make binaries PHONY in Makefile to skip timestamp check * List all available ops including pseudo-ops in example config * chore: fix go.mod --------- Co-authored-by: GImbrailo --- Makefile | 2 ++ cmd/commands/list_ops.go | 5 +++- docs/aws_kms.md | 1 - go.mod | 8 +++--- go.sum | 17 ++++++------ pkg/signatory/signatory.go | 4 +-- pkg/signatory/signatory_test.go | 47 ++++++++++++++++++++++++++++++++- pkg/signatory/utils.go | 3 ++- signatory.yaml | 14 +++++----- 9 files changed, 76 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index e90d8992..81ed3bf2 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ GOLANG_CROSS_VERSION ?= v1.21.0 all: signatory signatory-cli +# build is controlled by Go build system, so mark phony to ignore file timestamps +.PHONY: signatory signatory-cli signatory: CGO_ENABLED=1 go build -ldflags "-X $(COLLECTOR_PKG).GitRevision=$(GIT_REVISION) -X $(COLLECTOR_PKG).GitBranch=$(GIT_BRANCH)" ./cmd/signatory signatory-cli: diff --git a/cmd/commands/list_ops.go b/cmd/commands/list_ops.go index ec2d28c4..409b77fe 100644 --- a/cmd/commands/list_ops.go +++ b/cmd/commands/list_ops.go @@ -51,9 +51,12 @@ func NewListOps(c *Context) *cobra.Command { Short: "Print possible operation types inside the `generic` request", RunE: func(cmd *cobra.Command, args []string) error { var ops []string - for _, k := range encoding.ListVariants[latest.OperationContents]() { + for _, k := range latest.ListOperations() { ops = append(ops, k.OperationKind()) } + for _, op := range latest.ListPseudoOperations() { + ops = append(ops, op.PseudoOperation()) + } sort.Strings(ops) return listOpsTpl.Execute(os.Stdout, ops) }, diff --git a/docs/aws_kms.md b/docs/aws_kms.md index ddb1dedb..bc878620 100644 --- a/docs/aws_kms.md +++ b/docs/aws_kms.md @@ -75,7 +75,6 @@ tezos: - block - endorsement allowed_kinds: - # List of [endorsement, ballot, reveal, transaction, origination, delegation, seed_nonce_revelation, activate_account] - transaction - endorsement - reveal diff --git a/go.mod b/go.mod index 5c0ef366..d2db0e23 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/ecadlabs/goblst v1.0.0 github.com/ecadlabs/gotez/v2 v2.1.3 - github.com/go-playground/validator/v10 v10.16.0 + github.com/go-playground/validator/v10 v10.22.0 github.com/google/tink/go v1.7.0 github.com/google/uuid v1.4.0 github.com/gorilla/mux v1.8.1 @@ -54,7 +54,7 @@ require ( github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/ecadlabs/pretty v0.0.0-20230412124801-f948fc689a04 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect @@ -93,14 +93,14 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.25.0 golang.org/x/text v0.19.0 // indirect diff --git a/go.sum b/go.sum index f78daf3c..edb05038 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= @@ -92,8 +92,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -185,8 +185,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= @@ -242,7 +242,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -274,8 +273,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= diff --git a/pkg/signatory/signatory.go b/pkg/signatory/signatory.go index e5abebac..d311ab87 100644 --- a/pkg/signatory/signatory.go +++ b/pkg/signatory/signatory.go @@ -193,7 +193,7 @@ func matchFilter(policy *PublicKeyPolicy, req *SignRequest, msg protocol.SignReq if ops, ok := msg.(*protocol.GenericOperationSignRequest); ok { for _, op := range ops.Contents { - kind := op.OperationKind() + kind := core.GetOperationKind(op) allowed = false for _, k := range policy.AllowedOps { if kind == k { @@ -399,7 +399,7 @@ func (s *Signatory) Sign(ctx context.Context, req *SignRequest) (crypt.Signature l.Error(err) return nil, err } - return p.vault.SignMessage(ctx, message, p.key) + return p.vault.SignMessage(ctx, message, key) } var sig crypt.Signature diff --git a/pkg/signatory/signatory_test.go b/pkg/signatory/signatory_test.go index f848c7ca..05232e50 100644 --- a/pkg/signatory/signatory_test.go +++ b/pkg/signatory/signatory_test.go @@ -3,12 +3,19 @@ package signatory_test import ( + "bytes" "context" "encoding/hex" "fmt" "testing" + tz "github.com/ecadlabs/gotez/v2" "github.com/ecadlabs/gotez/v2/crypt" + "github.com/ecadlabs/gotez/v2/encoding" + "github.com/ecadlabs/gotez/v2/protocol" + "github.com/ecadlabs/gotez/v2/protocol/core" + "github.com/ecadlabs/gotez/v2/protocol/core/expression" + "github.com/ecadlabs/gotez/v2/protocol/latest" "github.com/ecadlabs/signatory/pkg/config" "github.com/ecadlabs/signatory/pkg/hashmap" "github.com/ecadlabs/signatory/pkg/signatory" @@ -59,6 +66,7 @@ func TestPolicy(t *testing.T) { type testCase struct { title string msg []byte + req protocol.SignRequest policy signatory.PublicKeyPolicy expected string } @@ -301,6 +309,34 @@ func TestPolicy(t *testing.T) { }, expected: "operation `update_consensus_key' is not allowed", }, + { + title: "Stake allowed", + req: &protocol.GenericOperationSignRequest{ + Branch: &tz.BlockHash{}, + Contents: []latest.OperationContents{ + &latest.Transaction{ + ManagerOperation: latest.ManagerOperation{ + Source: &tz.Ed25519PublicKeyHash{1, 2, 3}, + Fee: tz.BigUint{0x00}, + Counter: tz.BigUint{0x00}, + GasLimit: tz.BigUint{0x00}, + StorageLimit: tz.BigUint{0x00}, + }, + Amount: tz.BigUint{0x00}, + Destination: core.ImplicitContract{PublicKeyHash: &tz.Ed25519PublicKeyHash{1, 2, 3}}, + Parameters: tz.Some(latest.Parameters{ + Entrypoint: latest.EpStake{}, + Value: expression.Prim00(expression.Prim_Unit), + }), + }, + }, + }, + policy: signatory.PublicKeyPolicy{ + AllowedRequests: []string{"generic"}, + AllowedOps: []string{"stake"}, + LogPayloads: true, + }, + }, } priv, err := crypt.ParsePrivateKey([]byte(privateKey)) @@ -322,7 +358,16 @@ func TestPolicy(t *testing.T) { require.NoError(t, err) require.NoError(t, s.Unlock(context.Background())) - _, err = s.Sign(context.Background(), &signatory.SignRequest{PublicKeyHash: pk.Hash(), Message: c.msg}) + var msg []byte + if c.req != nil { + var buf bytes.Buffer + require.NoError(t, encoding.Encode(&buf, &c.req)) + msg = buf.Bytes() + } else { + msg = c.msg + } + + _, err = s.Sign(context.Background(), &signatory.SignRequest{PublicKeyHash: pk.Hash(), Message: msg}) if c.expected != "" { require.EqualError(t, err, c.expected) } else { diff --git a/pkg/signatory/utils.go b/pkg/signatory/utils.go index eae2efed..77fd6b2d 100644 --- a/pkg/signatory/utils.go +++ b/pkg/signatory/utils.go @@ -5,6 +5,7 @@ import ( "github.com/ecadlabs/gotez/v2/encoding" "github.com/ecadlabs/gotez/v2/protocol" + "github.com/ecadlabs/gotez/v2/protocol/core" ) func AuthenticatedBytesToSign(req *SignRequest) ([]byte, error) { @@ -27,7 +28,7 @@ type operationsStat map[string]int func getOperationsStat(u *protocol.GenericOperationSignRequest) operationsStat { ops := make(operationsStat) for _, o := range u.Contents { - ops[o.OperationKind()]++ + ops[core.GetOperationKind(o)]++ } return ops } diff --git a/signatory.yaml b/signatory.yaml index 02ba5b4e..f60a5add 100644 --- a/signatory.yaml +++ b/signatory.yaml @@ -66,12 +66,14 @@ tezos: allow: # List of [block, endorsement, failing_noop, generic, preendorsement] generic: - # List of [activate_account, ballot, delegation, double_baking_evidence, double_endorsement_evidence, - # double_preendorsement_evidence, endorsement, failing_noop, origination, preendorsement, proposals, - # register_global_constant, reveal, sc_rollup_add_messages, sc_rollup_cement, sc_rollup_originate, - # sc_rollup_publish, seed_nonce_revelation, set_deposits_limit, transaction, transfer_ticket, - # tx_rollup_commit, tx_rollup_dispatch_tickets, tx_rollup_finalize_commitment, tx_rollup_origination, - # tx_rollup_rejection, tx_rollup_remove_commitment, tx_rollup_return_bond, tx_rollup_submit_batch] + # List of + # [activate_account, attestation, attestation_with_dal, ballot, dal_publish_commitment, delegation, double_attestation_evidence, + # double_baking_evidence, double_preattestation_evidence, drain_delegate, failing_noop, finalize_unstake, increase_paid_storage, + # origination, preattestation, proposals, register_global_constant, reveal, seed_nonce_revelation, set_delegate_parameters, + # set_deposits_limit, signature_prefix, smart_rollup_add_messages, smart_rollup_cement, smart_rollup_execute_outbox_message, + # smart_rollup_originate, smart_rollup_publish, smart_rollup_recover_bond, smart_rollup_refute, smart_rollup_timeout, stake, + # transaction, transfer_ticket, unstake, update_consensus_key, vdf_revelation, zk_rollup_origination, zk_rollup_publish, + # zk_rollup_update] - transaction - endorsement block: