Skip to content

Commit

Permalink
Merge pull request #2732 from Tharsanan1/backendjwt
Browse files Browse the repository at this point in the history
Add backend JWt support to go enforcer
  • Loading branch information
Krishanx92 authored Jan 31, 2025
2 parents 3f1a19d + fae7fe2 commit b4893cd
Show file tree
Hide file tree
Showing 18 changed files with 484 additions and 467 deletions.
1 change: 0 additions & 1 deletion gateway/enforcer/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ require (
github.com/envoyproxy/go-control-plane v0.13.1
github.com/go-logr/logr v1.4.2
github.com/go-logr/zapr v1.3.0
github.com/golang-jwt/jwt/v4 v4.5.1
github.com/google/uuid v1.6.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/prometheus/client_golang v1.20.5
Expand Down
2 changes: 0 additions & 2 deletions gateway/enforcer/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
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.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
Expand Down
2 changes: 1 addition & 1 deletion gateway/enforcer/internal/authorization/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ func Validate(rch *requestconfig.Holder, subAppDataStore *datastore.Subscription
return immediateResponse
}
cfg.Logger.Info(fmt.Sprintf("Subscription validation successful for the request: %s", rch.MatchedResource.Path))

return nil
}
2 changes: 2 additions & 0 deletions gateway/enforcer/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type Server struct {
ExternalProcessingMaxHeaderLimit int `envconfig:"EXTERNAL_PROCESSING_MAX_HEADER_LIMIT" default:"8192"`
Logger logging.Logger
Metrics metrics
JWTGeneratorPublicKeyPath string `envconfig:"JWT_GENERATOR_PUBLIC_CERTIFICATE_PATH" default:"/home/wso2/security/keystore/mg.pem"`
JWTGeneratorPrivateKeyPath string `envconfig:"JWT_GENERATOR_PRIVATE_KEY_PATH" default:"/home/wso2/security/keystore/mg.key"`
}

type metrics struct {
Expand Down
10 changes: 5 additions & 5 deletions gateway/enforcer/internal/config/enforcer_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
* limitations under the License.
*
*/

package config

import (
"github.com/wso2/apk/gateway/enforcer/internal/dto"
config_from_adapter "github.com/wso2/apk/adapter/pkg/discovery/api/wso2/discovery/config/enforcer"
"github.com/wso2/apk/gateway/enforcer/internal/dto"
)

// EnforcerConfig is a struct that holds the enforcer configuration.
type EnforcerConfig struct {
JWTConfiguration *dto.JWTConfiguration
Analytics *config_from_adapter.Analytics
}
JWTConfiguration *dto.BackendJWTConfiguration
Analytics *config_from_adapter.Analytics
}
96 changes: 54 additions & 42 deletions gateway/enforcer/internal/datastore/api_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
package datastore

import (
"log"
"fmt"
"sync"

api "github.com/wso2/apk/adapter/pkg/discovery/api/wso2/discovery/api"
"github.com/wso2/apk/gateway/enforcer/internal/config"
"github.com/wso2/apk/gateway/enforcer/internal/dto"
"github.com/wso2/apk/gateway/enforcer/internal/requestconfig"
"github.com/wso2/apk/gateway/enforcer/internal/util"
Expand All @@ -32,13 +33,15 @@ type APIStore struct {
apis map[string]*requestconfig.API
mu sync.RWMutex
configStore *ConfigStore
cfg *config.Server
}

// NewAPIStore creates a new instance of APIStore.
func NewAPIStore(configStore *ConfigStore) *APIStore {
func NewAPIStore(configStore *ConfigStore, cfg *config.Server) *APIStore {
return &APIStore{
configStore: configStore,
// apis: make(map[string]*api.Api, 0),
cfg: cfg,
}
}

Expand All @@ -50,29 +53,29 @@ func (s *APIStore) AddAPIs(apis []*api.Api) {
s.apis = make(map[string]*requestconfig.API, len(apis))
for _, api := range apis {
customAPI := requestconfig.API{
Name: api.Title,
Version: api.Version,
Vhost: api.Vhost,
BasePath: api.BasePath,
APIType: api.ApiType,
EnvType: api.EnvType,
APILifeCycleState: api.ApiLifeCycleState,
AuthorizationHeader: "", // You might want to set this field if applicable
OrganizationID: api.OrganizationId,
UUID: api.Id,
Tier: api.Tier,
DisableAuthentication: api.DisableAuthentications,
DisableScopes: api.DisableScopes,
Resources: make([]requestconfig.Resource, 0),
IsMockedAPI: false, // You can add logic to determine if the API is mocked
MutualSSL: api.MutualSSL,
TransportSecurity: api.TransportSecurity,
ApplicationSecurity: api.ApplicationSecurity,
JwtConfigurationDto: convertBackendJWTTokenInfoToJWTConfig(api.BackendJWTTokenInfo),
SystemAPI: api.SystemAPI,
APIDefinition: api.ApiDefinitionFile,
Environment: api.Environment,
SubscriptionValidation: api.SubscriptionValidation,
Name: api.Title,
Version: api.Version,
Vhost: api.Vhost,
BasePath: api.BasePath,
APIType: api.ApiType,
EnvType: api.EnvType,
APILifeCycleState: api.ApiLifeCycleState,
AuthorizationHeader: "", // You might want to set this field if applicable
OrganizationID: api.OrganizationId,
UUID: api.Id,
Tier: api.Tier,
DisableAuthentication: api.DisableAuthentications,
DisableScopes: api.DisableScopes,
Resources: make([]requestconfig.Resource, 0),
IsMockedAPI: false, // You can add logic to determine if the API is mocked
MutualSSL: api.MutualSSL,
TransportSecurity: api.TransportSecurity,
ApplicationSecurity: api.ApplicationSecurity,
BackendJwtConfiguration: convertBackendJWTTokenInfoToJWTConfig(api.BackendJWTTokenInfo, s.cfg, fmt.Sprintf("%s-%s", api.Title, api.Version)),
SystemAPI: api.SystemAPI,
APIDefinition: api.ApiDefinitionFile,
Environment: api.Environment,
SubscriptionValidation: api.SubscriptionValidation,
// Endpoints: api.Endpoints,
// EndpointSecurity: convertSecurityInfoToEndpointSecurity(api.EndpointSecurity),
AiProvider: convertAIProviderToDTO(api.Aiprovider),
Expand All @@ -98,7 +101,7 @@ func (s *APIStore) AddAPIs(apis []*api.Api) {
customAPI.Resources = append(customAPI.Resources, resource)
}
}
log.Printf("Adding API: %+v", customAPI.JwtConfigurationDto)
s.cfg.Logger.Info(fmt.Sprintf("Adding API: %+v", customAPI.BackendJwtConfiguration))
s.apis[util.PrepareAPIKey(api.Vhost, api.BasePath, api.Version)] = &customAPI
}
}
Expand Down Expand Up @@ -173,33 +176,42 @@ func (s *APIStore) GetMatchedAPI(apiKey string) *requestconfig.API {
}

// ConvertBackendJWTTokenInfoToJWTConfig converts BackendJWTTokenInfo to JWTConfiguration.
func convertBackendJWTTokenInfoToJWTConfig(info *api.BackendJWTTokenInfo) *dto.JWTConfiguration {
func convertBackendJWTTokenInfoToJWTConfig(info *api.BackendJWTTokenInfo, cfg *config.Server, apiName string) *dto.BackendJWTConfiguration {
if info == nil {
return nil
}

// Convert CustomClaims from map[string]*Claim to map[string]ClaimValue
customClaims := make(map[string]dto.ClaimValue)
customClaims := make(map[string]*dto.ClaimValue)
for key, claim := range info.CustomClaims {
if claim != nil {
customClaims[key] = dto.ClaimValue{
customClaims[key] = &dto.ClaimValue{
Value: claim.Value,
Type: claim.Type,
}
}
}

return &dto.JWTConfiguration{
Enabled: info.Enabled,
JWTHeader: info.Header,
ConsumerDialectURI: "", // Add a default value or fetch if needed
SignatureAlgorithm: info.SigningAlgorithm,
Encoding: info.Encoding,
TokenIssuerDtoMap: make(map[string]dto.TokenIssuer), // Populate if required
JwtExcludedClaims: make(map[string]bool), // Populate if required
PublicCert: nil, // Add conversion logic if needed
PrivateKey: nil, // Add conversion logic if needed
TTL: int64(info.TokenTTL), // Convert int32 to int64
CustomClaims: customClaims,
publicCert, err := util.LoadCertificate(cfg.JWTGeneratorPublicKeyPath)
if err != nil {
cfg.Logger.Error(err, fmt.Sprintf("Error loading public cert. Marking API %s as backend jwt disabled.", apiName))
info.Enabled = false
}
privateKey, err := util.LoadPrivateKey(cfg.JWTGeneratorPrivateKeyPath)
if err != nil {
cfg.Logger.Error(err, fmt.Sprintf("Error loading private key. Marking API %s as backend jwt disabled. Path: %s", apiName, cfg.JWTGeneratorPrivateKeyPath))
info.Enabled = false
}
return &dto.BackendJWTConfiguration{
Enabled: info.Enabled,
JWTHeader: info.Header,
ConsumerDialectURI: "", // Add a default value or fetch if needed
SignatureAlgorithm: info.SigningAlgorithm,
Encoding: info.Encoding,
TokenIssuerDtoMap: make(map[string]dto.TokenIssuer), // Populate if required
JwtExcludedClaims: make(map[string]bool), // Populate if required
PublicCert: publicCert, // Add conversion logic if needed
PrivateKey: privateKey, // Add conversion logic if needed
TTL: int64(info.TokenTTL), // Convert int32 to int64
CustomClaims: customClaims,
}
}
2 changes: 1 addition & 1 deletion gateway/enforcer/internal/dto/claim_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ package dto

// ClaimValue represents the claim value
type ClaimValue struct {
Value interface{} `json:"value"` // Value of the claim (can be any type)
Value string `json:"value"` // Value of the claim (can be any type)
Type string `json:"type"` // Type of the claim
}
30 changes: 15 additions & 15 deletions gateway/enforcer/internal/dto/jwt_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@
package dto

import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
)

// JWTConfiguration represents the JWT configuration
type JWTConfiguration struct {
Enabled bool `json:"enabled"` // Whether JWT is enabled
JWTHeader string `json:"jwtHeader"` // JWT header name
ConsumerDialectURI string `json:"consumerDialectUri"` // URI for the consumer dialect
SignatureAlgorithm string `json:"signatureAlgorithm"` // Algorithm for signature
Encoding string `json:"encoding"` // Encoding type
TokenIssuerDtoMap map[string]TokenIssuer `json:"tokenIssuerDtoMap"` // Map of token issuers
JwtExcludedClaims map[string]bool `json:"jwtExcludedClaims"` // Excluded claims in JWT
PublicCert *x509.Certificate `json:"publicCert"` // Public certificate
PrivateKey *ecdsa.PrivateKey `json:"privateKey"` // Private key for signing JWT
TTL int64 `json:"ttl"` // Time to live for the JWT
CustomClaims map[string]ClaimValue `json:"customClaims"` // Custom claims
UseKid bool `json:"useKid"` // Whether to use kid
// BackendJWTConfiguration represents the JWT configuration
type BackendJWTConfiguration struct {
Enabled bool `json:"enabled"` // Whether JWT is enabled
JWTHeader string `json:"jwtHeader"` // JWT header name
ConsumerDialectURI string `json:"consumerDialectUri"` // URI for the consumer dialect
SignatureAlgorithm string `json:"signatureAlgorithm"` // Algorithm for signature
Encoding string `json:"encoding"` // Encoding type
TokenIssuerDtoMap map[string]TokenIssuer `json:"tokenIssuerDtoMap"` // Map of token issuers
JwtExcludedClaims map[string]bool `json:"jwtExcludedClaims"` // Excluded claims in JWT
PublicCert *x509.Certificate `json:"publicCert"` // Public certificate
PrivateKey *rsa.PrivateKey `json:"privateKey"` // Private key for signing JWT
TTL int64 `json:"ttl"` // Time to live for the JWT
CustomClaims map[string]*ClaimValue `json:"customClaims"` // Custom claims
UseKid bool `json:"useKid"` // Whether to use kid
}
20 changes: 19 additions & 1 deletion gateway/enforcer/internal/extproc/ext_proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/wso2/apk/gateway/enforcer/internal/config"
"github.com/wso2/apk/gateway/enforcer/internal/datastore"
"github.com/wso2/apk/gateway/enforcer/internal/dto"
"github.com/wso2/apk/gateway/enforcer/internal/jwtbackend"
"github.com/wso2/apk/gateway/enforcer/internal/logging"
"github.com/wso2/apk/gateway/enforcer/internal/ratelimit"
"github.com/wso2/apk/gateway/enforcer/internal/requestconfig"
Expand Down Expand Up @@ -189,6 +190,7 @@ func (s *ExternalProcessingServer) Process(srv envoy_service_proc_v3.ExternalPro
}
s.requestConfigHolder.ExternalProcessingEnvoyMetadata = metadata
s.requestConfigHolder.MatchedResource = httpHandler.GetMatchedResource(s.requestConfigHolder.MatchedAPI, *s.requestConfigHolder.ExternalProcessingEnvoyAttributes)
s.log.Info(fmt.Sprintf("Matched api bjc: %v", s.requestConfigHolder.MatchedAPI.BackendJwtConfiguration))
s.log.Info(fmt.Sprintf("Matched Resource: %v", s.requestConfigHolder.MatchedResource))
s.log.Info(fmt.Sprintf("req holder: %+v\n s: %+v", &s.requestConfigHolder, &s))
if !s.requestConfigHolder.MatchedResource.AuthenticationConfig.Disabled && !s.requestConfigHolder.MatchedAPI.DisableAuthentication {
Expand Down Expand Up @@ -216,6 +218,11 @@ func (s *ExternalProcessingServer) Process(srv envoy_service_proc_v3.ExternalPro
dynamicMetadataKeyValuePairs[orgAndRLPolicyMetadataKey] = fmt.Sprintf("%s-%s", s.requestConfigHolder.MatchedAPI.OrganizationID, s.requestConfigHolder.MatchedSubscription.RatelimitTier)
}
}
backendJWT := ""
if s.requestConfigHolder.MatchedAPI.BackendJwtConfiguration != nil && s.requestConfigHolder.MatchedAPI.BackendJwtConfiguration.Enabled {
backendJWT = jwtbackend.CreateBackendJWT(s.requestConfigHolder, s.cfg)
s.log.Sugar().Infof("generated backendJWT==%v", backendJWT)
}
rhq := &envoy_service_proc_v3.HeadersResponse{
Response: &envoy_service_proc_v3.CommonResponse{
HeaderMutation: &envoy_service_proc_v3.HeaderMutation{
Expand All @@ -232,6 +239,17 @@ func (s *ExternalProcessingServer) Process(srv envoy_service_proc_v3.ExternalPro
ClearRouteCache: true,
},
}
if backendJWT != "" {
rhq.Response.HeaderMutation.SetHeaders = append(rhq.Response.HeaderMutation.SetHeaders, &corev3.HeaderValueOption{
Header: &corev3.HeaderValue{
Key: s.requestConfigHolder.MatchedAPI.BackendJwtConfiguration.JWTHeader,
RawValue: []byte(attributes.ClusterName),
},

})
s.cfg.Logger.Info(fmt.Sprintf("Added backend JWT to the header: %s, header name: %s", backendJWT, s.requestConfigHolder.MatchedAPI.BackendJwtConfiguration.JWTHeader))
}

resp.Response = &envoy_service_proc_v3.ProcessingResponse_RequestHeaders{
RequestHeaders: rhq,
}
Expand Down Expand Up @@ -796,7 +814,7 @@ func buildDynamicMetadata(keyValuePairs *map[string]string) (*structpb.Struct, e
}

func (s *ExternalProcessingServer) prepareMetadataKeyValuePairAndAddTo(metadataKeyValuePair map[string]string) *map[string]string {
if s.requestConfigHolder.MatchedAPI != nil {
if s.requestConfigHolder != nil && s.requestConfigHolder.MatchedAPI != nil {
metadataKeyValuePair[analytics.APIIDKey] = s.requestConfigHolder.MatchedAPI.UUID
metadataKeyValuePair[analytics.APIContextKey] = s.requestConfigHolder.MatchedAPI.BasePath
metadataKeyValuePair[organizationMetadataKey] = s.requestConfigHolder.MatchedAPI.OrganizationID
Expand Down
Loading

0 comments on commit b4893cd

Please sign in to comment.