diff --git a/cmd/storage-server/command/auth/check_token.go b/cmd/storage-server/command/auth/check_token.go new file mode 100644 index 0000000..1b91164 --- /dev/null +++ b/cmd/storage-server/command/auth/check_token.go @@ -0,0 +1,75 @@ +package auth + +import ( + "encoding/json" + "fmt" + + "forge.cadoles.com/arcad/edge/cmd/storage-server/command/flag" + "forge.cadoles.com/arcad/edge/pkg/jwtutil" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/pkg/errors" + "github.com/urfave/cli/v2" +) + +func CheckToken() *cli.Command { + return &cli.Command{ + Name: "check-token", + Usage: "Validate and print the given token with the private key", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "token", + Required: true, + }, + flag.PrivateKey, + flag.PrivateKeySigningAlgorithm, + flag.PrivateKeyDefaultSize, + }, + Action: func(ctx *cli.Context) error { + privateKeyFile := flag.GetPrivateKey(ctx) + signingAlgorithm := flag.GetSigningAlgorithm(ctx) + privateKeyDefaultSize := flag.GetPrivateKeyDefaultSize(ctx) + rawToken := ctx.String("token") + + if rawToken == "" { + return errors.New("you must provide a value for --token flag") + } + + privateKey, err := jwtutil.LoadOrGenerateKey( + privateKeyFile, + privateKeyDefaultSize, + ) + if err != nil { + return errors.WithStack(err) + } + + keySet, err := jwtutil.NewKeySet() + if err != nil { + return errors.WithStack(err) + } + + err = jwtutil.AddKeyWithSigningAlgo(keySet, privateKey, jwa.SignatureAlgorithm(signingAlgorithm)) + if err != nil { + return errors.WithStack(err) + } + + token, err := jwtutil.Parse([]byte(rawToken), keySet) + if err != nil { + return errors.WithStack(err) + } + + claims, err := token.AsMap(ctx.Context) + if err != nil { + return errors.WithStack(err) + } + + json, err := json.MarshalIndent(claims, "", " ") + if err != nil { + return errors.WithStack(err) + } + + fmt.Println(string(json)) + + return nil + }, + } +} diff --git a/cmd/storage-server/command/auth/root.go b/cmd/storage-server/command/auth/root.go index 1dc3ddc..621498c 100644 --- a/cmd/storage-server/command/auth/root.go +++ b/cmd/storage-server/command/auth/root.go @@ -10,6 +10,7 @@ func Root() *cli.Command { Usage: "Auth related command", Subcommands: []*cli.Command{ NewToken(), + CheckToken(), }, } } diff --git a/cmd/storage-server/command/run.go b/cmd/storage-server/command/run.go index 613fd50..a9f9e25 100644 --- a/cmd/storage-server/command/run.go +++ b/cmd/storage-server/command/run.go @@ -41,6 +41,11 @@ func Run() *cli.Command { Aliases: []string{"addr"}, Value: ":3001", }, + &cli.IntFlag{ + Name: "log-level", + EnvVars: []string{"STORAGE_SERVER_LOG_LEVEL"}, + Value: int(logger.LevelError), + }, &cli.StringFlag{ Name: "blobstore-dsn-pattern", EnvVars: []string{"STORAGE_SERVER_BLOBSTORE_DSN_PATTERN"}, @@ -80,6 +85,9 @@ func Run() *cli.Command { privateKeyFile := flag.GetPrivateKey(ctx) signingAlgorithm := flag.GetSigningAlgorithm(ctx) privateKeyDefaultSize := flag.GetPrivateKeyDefaultSize(ctx) + logLevel := ctx.Int("log-level") + + logger.SetLevel(logger.Level(logLevel)) router := chi.NewRouter() @@ -91,11 +99,6 @@ func Run() *cli.Command { return errors.WithStack(err) } - publicKey, err := privateKey.PublicKey() - if err != nil { - return errors.WithStack(err) - } - getBlobStoreServer := createGetCachedStoreServer( func(dsn string) (storage.BlobStore, error) { return driver.NewBlobStore(dsn) @@ -125,12 +128,17 @@ func Run() *cli.Command { router.Use(middleware.RealIP) router.Use(middleware.Logger) - router.Use(authenticate(publicKey, jwa.SignatureAlgorithm(signingAlgorithm))) + + logger.Debug(ctx.Context, "using authentication", logger.F("privateKey", privateKeyFile), logger.F("signingAlgorithm", signingAlgorithm)) + + router.Use(authenticate(privateKey, jwa.SignatureAlgorithm(signingAlgorithm))) router.Handle("/blobstore", createStoreHandler(getBlobStoreServer, blobStoreDSNPattern, true, cacheSize, cacheTTL)) router.Handle("/documentstore", createStoreHandler(getDocumentStoreServer, documentStoreDSNPattern, true, cacheSize, cacheTTL)) router.Handle("/sharestore", createStoreHandler(getShareStoreServer, shareStoreDSNPattern, false, cacheSize, cacheTTL)) + logger.Info(ctx.Context, "listening", logger.F("addr", addr)) + if err := http.ListenAndServe(addr, router); err != nil { return errors.WithStack(err) } @@ -218,15 +226,17 @@ func authenticate(privateKey jwk.Key, signingAlgorithm jwa.SignatureAlgorithm) f ctx := r.Context() createKeySet.Do(func() { - err = privateKey.Set(jwk.AlgorithmKey, signingAlgorithm) + var keySet jwk.Set + + keySet, err = jwtutil.NewKeySet() if err != nil { + err = errors.WithStack(err) return } - var keySet jwk.Set - - keySet, err = jwtutil.NewKeySet(privateKey) + err = jwtutil.AddKeyWithSigningAlgo(keySet, privateKey, signingAlgorithm) if err != nil { + err = errors.WithStack(err) return } diff --git a/pkg/jwtutil/key.go b/pkg/jwtutil/key.go index 9a23627..5999594 100644 --- a/pkg/jwtutil/key.go +++ b/pkg/jwtutil/key.go @@ -1,11 +1,36 @@ package jwtutil import ( + "strings" + "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/pkg/errors" ) +func AddKeyWithSigningAlgo(keySet jwk.Set, key jwk.Key, signingAlgorithm jwa.SignatureAlgorithm) error { + addedKey := key + + if !strings.HasPrefix(string(signingAlgorithm), "HS") { + publicKey, err := key.PublicKey() + if err != nil { + return errors.WithStack(err) + } + + addedKey = publicKey + } + + if err := addedKey.Set(jwk.AlgorithmKey, signingAlgorithm); err != nil { + return errors.WithStack(err) + } + + if err := keySet.AddKey(addedKey); err != nil { + return errors.WithStack(err) + } + + return nil +} + func NewKeySet(keys ...jwk.Key) (jwk.Set, error) { set := jwk.NewSet() diff --git a/pkg/jwtutil/request.go b/pkg/jwtutil/request.go index 0d8602f..6c90953 100644 --- a/pkg/jwtutil/request.go +++ b/pkg/jwtutil/request.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/lestrrat-go/jwx/v2/jwk" - "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/pkg/errors" ) @@ -111,10 +110,7 @@ func FindToken(r *http.Request, getKeySet GetKeySetFunc, funcs ...FindTokenOptio return nil, errors.WithStack(ErrNoKeySet) } - token, err := jwt.Parse([]byte(rawToken), - jwt.WithKeySet(keySet, jws.WithRequireKid(false)), - jwt.WithValidate(true), - ) + token, err := Parse([]byte(rawToken), keySet) if err != nil { return nil, errors.WithStack(err) } diff --git a/pkg/jwtutil/token.go b/pkg/jwtutil/token.go index 24cfd50..51e08c9 100644 --- a/pkg/jwtutil/token.go +++ b/pkg/jwtutil/token.go @@ -5,6 +5,7 @@ import ( "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/oklog/ulid/v2" "github.com/pkg/errors" @@ -38,3 +39,15 @@ func SignedToken(key jwk.Key, signingAlgorithm jwa.SignatureAlgorithm, claims ma return rawToken, nil } + +func Parse(rawToken []byte, keySet jwk.Set) (jwt.Token, error) { + token, err := jwt.Parse(rawToken, + jwt.WithKeySet(keySet, jws.WithRequireKid(false)), + jwt.WithValidate(true), + ) + if err != nil { + return nil, errors.WithStack(err) + } + + return token, nil +}