Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign JSON Web Token (JWT) #772

Merged
merged 4 commits into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/colinmarc/hdfs v1.1.3
github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a // indirect
github.com/dgraph-io/ristretto v0.0.3
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dnaeon/go-vcr v1.1.0 // indirect
github.com/eclipse/paho.mqtt.golang v1.3.1
github.com/edsrzf/mmap-go v1.0.0
Expand Down
15 changes: 13 additions & 2 deletions lib/util/http/auth/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ func oAuth2FieldSpec() docs.FieldSpec {
)
}

func jwtFieldSpec() docs.FieldSpec {
return docs.FieldAdvanced("jwt",
"Allows you to specify JWT authentication.",
).WithChildren(
docs.FieldCommon("enabled", "Whether to use JWT authentication in requests."),
docs.FieldCommon("private_key_file", "A file with the PEM encoded via PKCS1 or PKCS8 as private key."),
docs.FieldCommon("signing_method", "A method used to sign the token such as RS256, RS384 or RS512."),
docs.FieldAdvanced("claims", "A value used to identify the claims that issued the JWT.").Map(),
)
}

// FieldSpecs returns a map of field specs for an auth type.
func FieldSpecs() docs.FieldSpecs {
return docs.FieldSpecs{
Expand All @@ -46,12 +57,12 @@ func FieldSpecs() docs.FieldSpecs {
}
}

// FieldSpecsExpanded includes OAuth2 fields that might not be appropriate for
// all components.
// FieldSpecsExpanded includes OAuth2 and JWT fields that might not be appropriate for all components.
func FieldSpecsExpanded() docs.FieldSpecs {
return docs.FieldSpecs{
oAuthFieldSpec(),
oAuth2FieldSpec(),
jwtFieldSpec(),
BasicAuthFieldSpec(),
}
}
94 changes: 94 additions & 0 deletions lib/util/http/auth/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package auth

import (
"crypto/rsa"
"fmt"
"io/ioutil"
"net/http"
"sync"

"github.com/dgrijalva/jwt-go"
)

//------------------------------------------------------------------------------

// JWTConfig holds the configuration parameters for an JWT exchange.
type JWTConfig struct {
Enabled bool `json:"enabled" yaml:"enabled"`
Claims jwt.MapClaims `json:"claims" yaml:"claims"`
SigningMethod string `json:"signing_method" yaml:"signing_method"`
PrivateKeyFile string `json:"private_key_file" yaml:"private_key_file"`

// internal private fields
rsaKeyMx *sync.Mutex
rsaKey *rsa.PrivateKey
}

// NewJWTConfig returns a new JWTConfig with default values.
func NewJWTConfig() JWTConfig {
return JWTConfig{
Enabled: false,
Claims: map[string]interface{}{},
SigningMethod: "",
PrivateKeyFile: "",
rsaKeyMx: &sync.Mutex{},
}
}

//------------------------------------------------------------------------------

// Sign method to sign an HTTP request for an JWT exchange.
func (j JWTConfig) Sign(req *http.Request) error {
if !j.Enabled {
return nil
}

if err := j.parsePrivateKey(); err != nil {
return err
}

var bearer *jwt.Token
switch j.SigningMethod {
case "RS256":
bearer = jwt.NewWithClaims(jwt.SigningMethodRS256, j.Claims)
case "RS384":
bearer = jwt.NewWithClaims(jwt.SigningMethodRS384, j.Claims)
case "RS512":
bearer = jwt.NewWithClaims(jwt.SigningMethodRS512, j.Claims)
default:
return fmt.Errorf("jwt signing method %s not acepted. Try with RS256, RS384 or RS512", j.SigningMethod)
}

ss, err := bearer.SignedString(j.rsaKey)
if err != nil {
return fmt.Errorf("failed to sign jwt: %v", err)
}

req.Header.Set("Authorization", "Bearer "+ss)
return nil
}

// parsePrivateKey parses once the RSA private key.
// Needs mutex locking as Sign might be called by parallel threads.
func (j JWTConfig) parsePrivateKey() error {
j.rsaKeyMx.Lock()
defer j.rsaKeyMx.Unlock()

if j.rsaKey != nil {
return nil
}

privateKey, err := ioutil.ReadFile(j.PrivateKeyFile)
if err != nil {
return fmt.Errorf("failed to read private key: %v", err)
}

j.rsaKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKey)
if err != nil {
return fmt.Errorf("failed to parse private key: %v", err)
}

return nil
}

//------------------------------------------------------------------------------
2 changes: 2 additions & 0 deletions lib/util/http/client/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Config struct {
ProxyURL string `json:"proxy_url" yaml:"proxy_url"`
auth.Config `json:",inline" yaml:",inline"`
OAuth2 auth.OAuth2Config `json:"oauth2" yaml:"oauth2"`
JWT auth.JWTConfig `json:"jwt" yaml:"jwt"`
}

// NewConfig creates a new Config with default values.
Expand All @@ -71,6 +72,7 @@ func NewConfig() Config {
TLS: tls.NewConfig(),
Config: auth.NewConfig(),
OAuth2: auth.NewOAuth2Config(),
JWT: auth.NewJWTConfig(),
}
}

Expand Down