diff --git a/cmd/lotus-shed/jwt.go b/cmd/lotus-shed/jwt.go new file mode 100644 index 000000000..d37359f71 --- /dev/null +++ b/cmd/lotus-shed/jwt.go @@ -0,0 +1,89 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/gbrlsnchs/jwt/v3" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/api/apistruct" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/modules" +) + +var jwtCmd = &cli.Command{ + Name: "jwt", + Usage: "work with lotus jwt secrets and tokens", + Description: `The subcommands of jwt provide helpful tools for working with jwt files without + having to run the lotus daemon.`, + Subcommands: []*cli.Command{ + jwtNewCmd, + }, +} + +var jwtNewCmd = &cli.Command{ + Name: "new", + Usage: "create a new jwt secret and token for lotus", + ArgsUsage: "", + Description: `Jwt tokens are used to authenticate api requests to the lotus daemon. + + The created jwt token have full privileges and should not be shared.`, + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + if !cctx.Args().Present() { + return fmt.Errorf("please specify a name") + } + + keyName := cctx.Args().First() + + sk, err := ioutil.ReadAll(io.LimitReader(rand.Reader, 32)) + if err != nil { + return err + } + + keyInfo := types.KeyInfo{ + Type: modules.KTJwtHmacSecret, + PrivateKey: sk, + } + + p := modules.JwtPayload{ + Allow: apistruct.AllPermissions, + } + + token, err := jwt.Sign(&p, jwt.NewHS256(keyInfo.PrivateKey)) + if err != nil { + return err + } + + filename := fmt.Sprintf("jwt-%s.jwts", keyName) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + + defer func() { + if err := file.Close(); err != nil { + log.Warnf("failed to close output file: %w", err) + } + }() + + bytes, err := json.Marshal(keyInfo) + if err != nil { + return err + } + + encoded := hex.EncodeToString(bytes) + if _, err := file.Write([]byte(encoded)); err != nil { + return err + } + + filenameToken := fmt.Sprintf("jwt-%s.token", keyName) + return ioutil.WriteFile(filenameToken, token, 0600) + }, +} diff --git a/cmd/lotus-shed/keyinfo.go b/cmd/lotus-shed/keyinfo.go index d4272e5bc..b3a42e1c1 100644 --- a/cmd/lotus-shed/keyinfo.go +++ b/cmd/lotus-shed/keyinfo.go @@ -330,7 +330,7 @@ var keyinfoNewCmd = &cli.Command{ filename = strings.ReplaceAll(filename, "", keyAddr) filename = strings.ReplaceAll(filename, "", keyType) - file, err := os.Create(filename) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 7a2189c73..30e7ff32d 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -19,6 +19,7 @@ func main() { base16Cmd, bitFieldCmd, keyinfoCmd, + jwtCmd, noncefix, bigIntParseCmd, staterootCmd, diff --git a/node/modules/core.go b/node/modules/core.go index d72dad148..6d2ca64d0 100644 --- a/node/modules/core.go +++ b/node/modules/core.go @@ -37,8 +37,9 @@ func RecordValidator(ps peerstore.Peerstore) record.Validator { } const JWTSecretName = "auth-jwt-private" //nolint:gosec +const KTJwtHmacSecret = "jwt-hmac-secret" -type jwtPayload struct { +type JwtPayload struct { Allow []auth.Permission } @@ -54,7 +55,7 @@ func APISecret(keystore types.KeyStore, lr repo.LockedRepo) (*dtypes.APIAlg, err } key = types.KeyInfo{ - Type: "jwt-hmac-secret", + Type: KTJwtHmacSecret, PrivateKey: sk, } @@ -63,7 +64,7 @@ func APISecret(keystore types.KeyStore, lr repo.LockedRepo) (*dtypes.APIAlg, err } // TODO: make this configurable - p := jwtPayload{ + p := JwtPayload{ Allow: apistruct.AllPermissions, }