From c7f1d0fc3325a807d981f628e7b7565dca060b82 Mon Sep 17 00:00:00 2001 From: aeneasr Date: Wed, 25 Sep 2019 14:40:43 +0200 Subject: [PATCH] rule: Add migration capabilities Adds the ability to modify rules with backwards compatibility. Closes #266 --- .goreleaser.yml | 2 +- Makefile | 2 +- UPGRADE.md | 8 ++--- cmd/root.go | 6 ---- cmd/serve.go | 3 +- cmd/version.go | 8 +++-- go.mod | 1 + go.sum | 2 ++ rule/rule.go | 31 ++++++++++++++++++ rule/rule_migrator.go | 46 ++++++++++++++++++++++++++ rule/rule_migrator_test.go | 67 ++++++++++++++++++++++++++++++++++++++ x/version.go | 9 +++++ 12 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 rule/rule_migrator.go create mode 100644 rule/rule_migrator_test.go create mode 100644 x/version.go diff --git a/.goreleaser.yml b/.goreleaser.yml index 94303e6c0d..d5f1e312cf 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -16,7 +16,7 @@ builds: flags: - -a ldflags: - - -s -w -X github.com/ory/oathkeeper/cmd.Version={{.Env.RELEASE_NAME}} -X github.com/ory/oathkeeper/cmd.Commit={{.FullCommit}} -X github.com/ory/oathkeeper/cmd.Date={{.Date}} + - -s -w -X github.com/ory/oathkeeper/x.Version={{.Env.RELEASE_NAME}} -X github.com/ory/oathkeeper/x.Commit={{.FullCommit}} -X github.com/ory/oathkeeper/x.Date={{.Date}} binary: oathkeeper env: - CGO_ENABLED=0 diff --git a/Makefile b/Makefile index c7a531428a..aa1c679251 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ install-stable: git checkout $$OATHKEEPER_LATEST packr2 GO111MODULE=on go install \ - -ldflags "-X github.com/ory/oathkeeper/cmd.Version=$$OATHKEEPER_LATEST -X github.com/ory/oathkeeper/cmd.Date=`TZ=UTC date -u '+%Y-%m-%dT%H:%M:%SZ'` -X github.com/ory/oathkeeper/cmd.Commit=`git rev-parse HEAD`" \ + -ldflags "-X github.com/ory/oathkeeper/x.Version=$$OATHKEEPER_LATEST -X github.com/ory/oathkeeper/x.Date=`TZ=UTC date -u '+%Y-%m-%dT%H:%M:%SZ'` -X github.com/ory/oathkeeper/x.Commit=`git rev-parse HEAD`" \ . packr2 clean git checkout master diff --git a/UPGRADE.md b/UPGRADE.md index 029b2462fb..ea08d4ef00 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -58,10 +58,10 @@ Previously, this value was only configurable in the global config. Now, it can be set on a per rule basis as well as globally. The global config will always be used as a fallback when no access rule specific configuration is set. -For this to work, the ORY Oathkeeper global configuration file (`~/.oathkeeper.yaml`) has changed when it -comes to mutators, authenticaotrs, and authorizers. Instead of defining the -config at the same level as the `enabled` flag, it is now nested in a subkey -"config": +For this to work, the ORY Oathkeeper global configuration file +(`~/.oathkeeper.yaml`) has changed when it comes to mutators, authenticaotrs, +and authorizers. Instead of defining the config at the same level as the +`enabled` flag, it is now nested in a subkey "config": ``` authorizers: diff --git a/cmd/root.go b/cmd/root.go index fef440875c..f16b23369e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -37,12 +37,6 @@ import ( "github.com/ory/x/logrusx" ) -var ( - Version = "master" - Date = "undefined" - Commit = "undefined" -) - var schemas = packr.New("schemas", "../.schemas") // RootCmd represents the base command when called without any subcommands diff --git a/cmd/serve.go b/cmd/serve.go index f6781d51ec..808d995607 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -22,6 +22,7 @@ package cmd import ( "github.com/ory/oathkeeper/cmd/server" + "github.com/ory/oathkeeper/x" "github.com/ory/x/logrusx" "github.com/ory/x/viperx" @@ -41,7 +42,7 @@ on configuration options, open the configuration documentation: >> https://www.ory.sh/docs/oathkeeper/configuration << `, - Run: server.RunServe(Version, Commit, Date), + Run: server.RunServe(x.Version, x.Commit, x.Date), } func init() { diff --git a/cmd/version.go b/cmd/version.go index 7a084aabd4..468e163310 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -24,6 +24,8 @@ import ( "fmt" "github.com/spf13/cobra" + + "github.com/ory/oathkeeper/x" ) // versionCmd represents the version command @@ -31,9 +33,9 @@ var versionCmd = &cobra.Command{ Use: "version", Short: "Display this binary's version, build time and git hash of this build", Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Version: %s\n", Version) - fmt.Printf("Git Hash: %s\n", Commit) - fmt.Printf("Build Time: %s\n", Date) + fmt.Printf("Version: %s\n", x.Version) + fmt.Printf("Git Hash: %s\n", x.Commit) + fmt.Printf("Build Time: %s\n", x.Date) }, } diff --git a/go.mod b/go.mod index 98db5b7c6c..449cfbb31e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Microsoft/go-winio v0.4.12 // indirect github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 + github.com/blang/semver v3.5.1+incompatible github.com/bxcodec/faker v2.0.1+incompatible github.com/cenkalti/backoff v2.1.1+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible diff --git a/go.sum b/go.sum index 2e2a6841bd..d9b63efff6 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 h1:irR1cO6 github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bxcodec/faker v2.0.1+incompatible h1:P0KUpUw5w6WJXwrPfv35oc91i4d8nf40Nwln+M/+faA= diff --git a/rule/rule.go b/rule/rule.go index 74d28398f6..5bc7c5e987 100644 --- a/rule/rule.go +++ b/rule/rule.go @@ -70,6 +70,10 @@ type Rule struct { // You will need this ID later on to update or delete the rule. ID string `json:"id"` + // Version represents the access rule version. Should match one of ORY Oathkeepers release versions. Supported since + // v0.20.0-beta.1+oryOS.14. + Version string `json:"version"` + // Description is a human readable description of this rule. Description string `json:"description"` @@ -110,6 +114,8 @@ type Upstream struct { URL string `json:"url"` } +var _ json.Unmarshaler = new(Rule) + func NewRule() *Rule { return &Rule{ Match: RuleMatch{}, @@ -118,6 +124,31 @@ func NewRule() *Rule { } } +func (r *Rule) UnmarshalJSON(raw []byte) error { + var rr struct { + ID string `json:"id"` + Version string `json:"version"` + Description string `json:"description"` + Match RuleMatch `json:"match"` + Authenticators []RuleHandler `json:"authenticators"` + Authorizer RuleHandler `json:"authorizer"` + Mutators []RuleHandler `json:"mutators"` + Upstream Upstream `json:"upstream"` + } + + transformed, err := migrateRuleJSON(raw) + if err != nil { + return err + } + + if err := errors.WithStack(json.Unmarshal(transformed, &rr)); err != nil { + return err + } + + *r = rr + return nil +} + // GetID returns the rule's ID. func (r *Rule) GetID() string { return r.ID diff --git a/rule/rule_migrator.go b/rule/rule_migrator.go new file mode 100644 index 0000000000..f5c02795c9 --- /dev/null +++ b/rule/rule_migrator.go @@ -0,0 +1,46 @@ +package rule + +import ( + "strings" + + "github.com/blang/semver" + "github.com/pkg/errors" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" + + "github.com/ory/herodot" + "github.com/ory/x/stringsx" + + "github.com/ory/oathkeeper/x" +) + +func migrateRuleJSON(raw []byte) ([]byte, error) { + rv := strings.TrimPrefix( + stringsx.Coalesce( + gjson.GetBytes(raw, "version").String(), + x.Version, + x.UnknownVersion, + ), + "v", + ) + + if rv == x.UnknownVersion { + return raw, nil + } + + version, err := semver.Make(rv) + if err != nil { + return nil, errors.WithStack(err) + } + + raw, err = sjson.SetBytes(raw, "version", strings.Split(x.Version, "+")[0]) + if err != nil { + return nil, errors.WithStack(err) + } + + if semver.MustParseRange(">=0.19.0-alpha.0")(version) { + return raw, nil + } + + return nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unknown access rule version %s, unable to migrate.", version.String())) +} diff --git a/rule/rule_migrator_test.go b/rule/rule_migrator_test.go new file mode 100644 index 0000000000..42e44946f3 --- /dev/null +++ b/rule/rule_migrator_test.go @@ -0,0 +1,67 @@ +package rule + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ory/oathkeeper/x" +) + +func TestRuleMigration(t *testing.T) { + for k, tc := range []struct { + d string + in string + out string + expectErr bool + version string + }{ + { + d: "should work with v0.19.0-beta.1", + in: `{}`, + out: `{"id":"","version":"v0.19.0-beta.1","description":"","match":{"methods":null,"url":""},"authenticators":null,"authorizer":{"handler":"","config":null},"mutators":null,"upstream":{"preserve_host":false,"strip_path":"","url":""}}`, + version: "v0.19.0-beta.1", + }, + { + d: "should work with v0.19.0-beta.1+oryOS.12", + in: `{}`, + out: `{"id":"","version":"v0.19.0-beta.1","description":"","match":{"methods":null,"url":""},"authenticators":null,"authorizer":{"handler":"","config":null},"mutators":null,"upstream":{"preserve_host":false,"strip_path":"","url":""}}`, + version: "v0.19.0-beta.1+oryOS.12", + }, + { + d: "should work with v0.19.0-beta.1", + in: `{"version":"v0.19.0-beta.1"}`, + out: `{"id":"","version":"v0.19.0-beta.1","description":"","match":{"methods":null,"url":""},"authenticators":null,"authorizer":{"handler":"","config":null},"mutators":null,"upstream":{"preserve_host":false,"strip_path":"","url":""}}`, + version: "v0.19.0-beta.1", + }, + { + d: "should work with 0.19.0-beta.1", + in: `{"version":"0.19.0-beta.1"}`, + out: `{"id":"","version":"v0.19.0-beta.1","description":"","match":{"methods":null,"url":""},"authenticators":null,"authorizer":{"handler":"","config":null},"mutators":null,"upstream":{"preserve_host":false,"strip_path":"","url":""}}`, + version: "v0.19.0-beta.1+oryOS.12", + }, + } { + t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) { + var r Rule + + x.Version = tc.version + err := json.NewDecoder(bytes.NewBufferString(tc.in)).Decode(&r) + x.Version = x.UnknownVersion + + if tc.expectErr { + require.Error(t, err) + return + } + + require.NoError(t, err, "%+v", err) + + var out bytes.Buffer + require.NoError(t, json.NewEncoder(&out).Encode(&r)) + assert.JSONEq(t, tc.out, out.String(), "%s", out.String()) + }) + } +} diff --git a/x/version.go b/x/version.go new file mode 100644 index 0000000000..a430512e8c --- /dev/null +++ b/x/version.go @@ -0,0 +1,9 @@ +package x + +const UnknownVersion = "master" + +var ( + Version = "master" + Date = "undefined" + Commit = "undefined" +)