Skip to content

Commit

Permalink
Glooctl license check (#9871)
Browse files Browse the repository at this point in the history
* adding license check to glooctl

* adding changelog

* gentool

* glooctl license validate

* fix typos, code review

* renaming valiation

* more typos

---------

Co-authored-by: soloio-bulldozer[bot] <48420018+soloio-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
asayah and soloio-bulldozer[bot] authored Aug 9, 2024
1 parent df93f5f commit e8ea626
Show file tree
Hide file tree
Showing 17 changed files with 321 additions and 20 deletions.
6 changes: 6 additions & 0 deletions changelog/v1.18.0-beta14/glooctl-license-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
changelog:
- type: NEW_FEATURE
issueLink: https://github.com/solo-io/gloo/issues/3520
resolvesIssue: false
description: >-
Check the validity of Gloo Gateway License using `glooctl license validate --license-key <key>`.
1 change: 1 addition & 0 deletions docs/content/reference/cli/glooctl.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ glooctl is the unified CLI for Gloo.
* [glooctl init-plugin-manager](../glooctl_init-plugin-manager) - Install the Gloo Gateway Enterprise CLI plugin manager
* [glooctl install](../glooctl_install) - install gloo on different platforms
* [glooctl istio](../glooctl_istio) - Commands for interacting with Istio in Gloo
* [glooctl license](../glooctl_license) - subcommands for interacting with the license
* [glooctl plugin](../glooctl_plugin) - Commands for interacting with glooctl plugins
* [glooctl proxy](../glooctl_proxy) - interact with proxy instances managed by Gloo
* [glooctl remove](../glooctl_remove) - remove configuration items from a top-level Gloo resource
Expand Down
35 changes: 35 additions & 0 deletions docs/content/reference/cli/glooctl_license.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "glooctl license"
weight: 5
---
## glooctl license

subcommands for interacting with the license

### Options

```
-h, --help help for license
```

### Options inherited from parent commands

```
-c, --config string set the path to the glooctl config file (default "<home_directory>/.gloo/glooctl-config.yaml")
--consul-address string address of the Consul server. Use with --use-consul (default "127.0.0.1:8500")
--consul-allow-stale-reads Allows reading using Consul's stale consistency mode.
--consul-datacenter string Datacenter to use. If not provided, the default agent datacenter is used. Use with --use-consul
--consul-root-key string key prefix for for Consul key-value storage. (default "gloo")
--consul-scheme string URI scheme for the Consul server. Use with --use-consul (default "http")
--consul-token string Token is used to provide a per-request ACL token which overrides the agent's default token. Use with --use-consul
-i, --interactive use interactive mode
--kube-context string kube context to use when interacting with kubernetes
--kubeconfig string kubeconfig to use, if not standard one
--use-consul use Consul Key-Value storage as the backend for reading and writing config (VirtualServices, Upstreams, and Proxies)
```

### SEE ALSO

* [glooctl](../glooctl) - CLI for Gloo
* [glooctl license validate](../glooctl_license_validate) - Check Gloo Gateway License Validity

45 changes: 45 additions & 0 deletions docs/content/reference/cli/glooctl_license_validate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: "glooctl license validate"
weight: 5
---
## glooctl license validate

Check Gloo Gateway License Validity

### Synopsis

Checking Gloo Gateway license Validity.

Usage: `glooctl license validate [--license-key license-key]`

```
glooctl license validate [flags]
```

### Options

```
-h, --help help for validate
--license-key string license key to validate
```

### Options inherited from parent commands

```
-c, --config string set the path to the glooctl config file (default "<home_directory>/.gloo/glooctl-config.yaml")
--consul-address string address of the Consul server. Use with --use-consul (default "127.0.0.1:8500")
--consul-allow-stale-reads Allows reading using Consul's stale consistency mode.
--consul-datacenter string Datacenter to use. If not provided, the default agent datacenter is used. Use with --use-consul
--consul-root-key string key prefix for for Consul key-value storage. (default "gloo")
--consul-scheme string URI scheme for the Consul server. Use with --use-consul (default "http")
--consul-token string Token is used to provide a per-request ACL token which overrides the agent's default token. Use with --use-consul
-i, --interactive use interactive mode
--kube-context string kube context to use when interacting with kubernetes
--kubeconfig string kubeconfig to use, if not standard one
--use-consul use Consul Key-Value storage as the backend for reading and writing config (VirtualServices, Upstreams, and Proxies)
```

### SEE ALSO

* [glooctl license](../glooctl_license) - subcommands for interacting with the license

1 change: 1 addition & 0 deletions docs/content/static/content/osa_provided.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Name|Version|License
[go-swagger/go-swagger](https://github.com/go-swagger/go-swagger)|v0.21.0|Apache License 2.0
[gogo/googleapis](https://github.com/gogo/googleapis)|v1.4.0|Apache License 2.0
[gogo/protobuf](https://github.com/gogo/protobuf)|v1.3.2|BSD 3-clause "New" or "Revised" License
[jwt/v4](https://github.com/golang-jwt/jwt)|v4.5.0|MIT License
[golang/protobuf](https://github.com/golang/protobuf)|v1.5.3|BSD 3-clause "New" or "Revised" License
[google/go-cmp](https://github.com/google/go-cmp)|v0.6.0|BSD 3-clause "New" or "Revised" License
[google/go-github](https://github.com/google/go-github)|v17.0.0+incompatible|BSD 3-clause "New" or "Revised" License
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ require (
github.com/ahmetb/gen-crd-api-reference-docs v0.3.1-0.20240214155107-6cf1ede4da61
github.com/avast/retry-go/v4 v4.3.3
github.com/go-logr/zapr v1.3.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.3.1
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand Down
13 changes: 13 additions & 0 deletions projects/gloo/cli/pkg/cmd/license/license_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package license

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestLicense(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "License Suite")
}
16 changes: 16 additions & 0 deletions projects/gloo/cli/pkg/cmd/license/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package license

import (
"github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd/options"
"github.com/spf13/cobra"
)

func RootCmd(opts *options.Options) *cobra.Command {
cmd := &cobra.Command{
Use: "license",
Aliases: []string{"l"},
Short: "subcommands for interacting with the license",
}
cmd.AddCommand(License(opts))
return cmd
}
105 changes: 105 additions & 0 deletions projects/gloo/cli/pkg/cmd/license/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package license

import (
"encoding/base64"
"fmt"
"strings"
"time"

"github.com/golang-jwt/jwt/v4"
"github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd/options"
"github.com/solo-io/gloo/projects/gloo/cli/pkg/flagutils"
"github.com/spf13/cobra"
)

type AddOn struct {
Addon int `json:"Addon"`
ExpiresAt int64 `json:"ExpiresAt"`
LicenseType string `json:"LicenseType"`
}

type LicenseClaims struct {
AddOns []AddOn `json:"addOns"`
ExpirationDate int64 `json:"exp"`
CreationDate int64 `json:"iat"`
LicenseType string `json:"lt"`
Product string `json:"product"`
jwt.RegisteredClaims
}

type LicenseLegacyClaims struct {
AddOns string `json:"addOns"`
Exp int64 `json:"exp"`
Iat int64 `json:"iat"`
Key string `json:"k"`
LicType string `json:"lt"`
Product string `json:"product"`
jwt.RegisteredClaims
}

func License(opts *options.Options) *cobra.Command {
cmd := &cobra.Command{
Use: "validate",
Aliases: []string{"v", "validate"},
Short: "Check Gloo Gateway License Validity",
Long: "Checking Gloo Gateway license Validity.\n\n" +
"" +
"Usage: `glooctl license validate [--license-key license-key]`",

RunE: func(cmd *cobra.Command, args []string) error {
licenseKey := opts.ValidateLicense.LicenseKey
if strings.Count(licenseKey, ".") == 1 {
return validateLegacyLicense(opts.ValidateLicense.LicenseKey)
}
return validateLicense(opts.ValidateLicense.LicenseKey)

}}
flags := cmd.Flags()
flagutils.AddLicenseValidationFlag(flags, &opts.ValidateLicense.LicenseKey)
cmd.MarkFlagRequired(flagutils.LicenseFlag)
return cmd
}

func validateLicense(licenseKey string) error {
var licenseClaims LicenseClaims

_, _, err := new(jwt.Parser).ParseUnverified(licenseKey, &licenseClaims)
if err != nil {
return fmt.Errorf("can't parse license key")
}
fmt.Printf(formatLicenseDetail(licenseClaims.CreationDate, licenseClaims.ExpirationDate, licenseClaims.Product, licenseClaims.LicenseType == "trial"))
return nil
}

func validateLegacyLicense(licenseKey string) error {
var licenseLegacyClaim LicenseLegacyClaims
var standardizedLicenseKey = base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) + "." + licenseKey
_, _, err := new(jwt.Parser).ParseUnverified(standardizedLicenseKey, &licenseLegacyClaim)
if err != nil {
return fmt.Errorf("can't parse license key")
}
fmt.Printf(formatLicenseDetail(licenseLegacyClaim.Iat, licenseLegacyClaim.Exp, licenseLegacyClaim.Product, licenseLegacyClaim.LicType == "trial"))
return nil
}

func formatLicenseDetail(creationTime int64, expirationTime int64, product string, isTrial bool) string {
var productName = "unknown"
switch product {
case "gloo":
productName = "Gloo Gateway"
}
var res = ""
if isTrial {
res += fmt.Sprintln("This a trial license for", productName)
} else {
res += fmt.Sprintln("This an enterprise license for", productName)
}
res += fmt.Sprintln("This license was created on:", time.Unix(creationTime, 0))
if expirationTime < time.Now().Unix() {
res += fmt.Sprintln("This license is expired since:", time.Unix(expirationTime, 0))
} else {
res += fmt.Sprintln("This license is valid until:", time.Unix(expirationTime, 0))
}

return res
}
59 changes: 59 additions & 0 deletions projects/gloo/cli/pkg/cmd/license/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package license

import (
"time"

"github.com/golang-jwt/jwt/v4"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("License Validate", func() {

licenseClaims := LicenseClaims{
AddOns: []AddOn{{Addon: 1, ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), LicenseType: "trial"}},
ExpirationDate: time.Now().Add(24 * time.Hour).Unix(),
CreationDate: time.Now().Add(-24 * time.Hour).Unix(),
LicenseType: "trial",
Product: "gloo",
}

expiredLicenseClaims := LicenseClaims{
AddOns: []AddOn{{Addon: 1, ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), LicenseType: "trial"}},
ExpirationDate: time.Now().Add(-24 * time.Hour).Unix(),
CreationDate: time.Now().Add(-48 * time.Hour).Unix(),
LicenseType: "ent",
Product: "gloo",
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, licenseClaims)
tokenString, err := token.SignedString([]byte("secret"))

It("should verify a valid license", func() {
err = validateLicense(tokenString)
Expect(err).ToNot(HaveOccurred())
})

It("should fail to verify an invalid license", func() {
invalidLicenseKey := "invalid"

err := validateLicense(invalidLicenseKey)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("can't parse license key"))
})

It("should print correct values for valid license", func() {

res := formatLicenseDetail(licenseClaims.CreationDate, licenseClaims.ExpirationDate, licenseClaims.Product, licenseClaims.LicenseType == "trial")
Expect(res).To(ContainSubstring("This a trial license"))
Expect(res).To(ContainSubstring("This license is valid until"))
})

It("should print correct values for expired license", func() {

res := formatLicenseDetail(expiredLicenseClaims.CreationDate, expiredLicenseClaims.ExpirationDate, expiredLicenseClaims.Product, expiredLicenseClaims.LicenseType == "trial")
Expect(res).To(ContainSubstring("This an enterprise license for Gloo Gateway"))
Expect(res).To(ContainSubstring("This license is expired since"))
})

})
39 changes: 22 additions & 17 deletions projects/gloo/cli/pkg/cmd/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,24 @@ import (
)

type Options struct {
Metadata core.Metadata
Top Top
Install Install
Uninstall Uninstall
Proxy Proxy
Upgrade Upgrade
Create Create
Delete Delete
Edit Edit
Route Route
Get Get
Add Add
Istio Istio
Remove Remove
Cluster Cluster
Check Check
CheckCRD CheckCRD
Metadata core.Metadata
Top Top
Install Install
Uninstall Uninstall
Proxy Proxy
Upgrade Upgrade
Create Create
Delete Delete
Edit Edit
Route Route
Get Get
Add Add
Istio Istio
Remove Remove
Cluster Cluster
Check Check
CheckCRD CheckCRD
ValidateLicense ValidateLicense
}
type Top struct {
contextoptions.ContextAccessible
Expand Down Expand Up @@ -491,3 +492,7 @@ type CheckCRD struct {
LocalChart string
ShowYaml bool
}

type ValidateLicense struct {
LicenseKey string
}
2 changes: 2 additions & 0 deletions projects/gloo/cli/pkg/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd/initpluginmanager"
"github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd/install"
"github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd/istio"
"github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd/license"
"github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd/options"
"github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd/plugin"
"github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd/remove"
Expand Down Expand Up @@ -128,6 +129,7 @@ func CommandWithContext(ctx context.Context) *cobra.Command {
federation.RootCmd(opts),
plugin.RootCmd(opts),
istio.RootCmd(opts),
license.RootCmd(opts),
initpluginmanager.Command(context.Background()),
// TODO: re-enable this when it's working again
// kubegateway.InstallCmd(opts),
Expand Down
2 changes: 1 addition & 1 deletion projects/gloo/cli/pkg/flagutils/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func AddGlooInstallFlags(set *pflag.FlagSet, install *options.Install) {

func AddEnterpriseInstallFlags(set *pflag.FlagSet, install *options.Install) {
set.BoolVarP(&install.DryRun, "dry-run", "d", false, "Dump the raw installation yaml instead of applying it to kubernetes")
set.StringVar(&install.LicenseKey, "license-key", "", "License key to activate GlooE features")
set.StringVar(&install.LicenseKey, LicenseFlag, "", "License key to activate GlooE features")
set.BoolVar(&install.WithGlooFed, "with-gloo-fed", true, "Install Gloo-Fed alongside Gloo Enterprise")
// Gloo-fed
set.StringSliceVar(&install.Federation.HelmChartValueFileNames, "gloo-fed-values", []string{}, "List of files with value overrides for the Gloo Fed Helm chart, (e.g. --values file1,file2 or --values file1 --values file2)")
Expand Down
Loading

0 comments on commit e8ea626

Please sign in to comment.