Skip to content

Commit

Permalink
add authenticator support
Browse files Browse the repository at this point in the history
  • Loading branch information
tharindu1st committed Feb 24, 2025
1 parent 3843aee commit d061a66
Show file tree
Hide file tree
Showing 26 changed files with 581 additions and 216 deletions.
4 changes: 2 additions & 2 deletions adapter/config/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func ReadConfigs() *Config {
}
parseErr := toml.Unmarshal(content, adapterConfig)
if parseErr != nil {
loggerConfig.ErrorC(logging.PrintError(logging.Error1002, logging.BLOCKER, "Error parsing the configurations, error: %v", parseErr.Error()))
loggerConfig.ErrorC(logging.PrintError(logging.Error1002, logging.BLOCKER, "Error parsing the configurations, error: %v", parseErr))
return
}

Expand All @@ -81,7 +81,7 @@ func ReadConfigs() *Config {
// validate the analytics configuration values
validationErr := validateAnalyticsConfigs(adapterConfig)
if validationErr != nil {
loggerConfig.ErrorC(logging.PrintError(logging.Error1002, logging.BLOCKER, "Error validating the configurations, error: %v", parseErr.Error()))
loggerConfig.ErrorC(logging.PrintError(logging.Error1002, logging.BLOCKER, "Error validating the configurations, error: %v", parseErr))
return
}
})
Expand Down
20 changes: 10 additions & 10 deletions adapter/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,19 @@ type xRateLimitHeaders struct {
}

type enforcer struct {
Security security
AuthService authService
JwtGenerator jwtGenerator
Cache cache
JwtIssuer jwtIssuer
Management management
Security security `toml:"security"`
AuthService authService `toml:"authService"`
JwtGenerator jwtGenerator `toml:"jwtGenerator"`
Cache cache `toml:"cache"`
JwtIssuer jwtIssuer `toml:"jwtIssuer"`
Management management `toml:"management"`
RestServer restServer
Filters []filter
Metrics Metrics
MandateSubscriptionValidation bool
MandateInternalKeyValidation bool
Client httpClient
Cors cors
Cors cors `toml:"cors"`
}

// Cors represents the configurations related to Cross-Origin Resource Sharing
Expand Down Expand Up @@ -232,9 +232,9 @@ type upstreamRetry struct {
}

type security struct {
InternalKey internalKey
APIkey apiKey
MutualSSL mutualSSL
InternalKey internalKey `toml:"internalKey"`
APIkey apiKey `toml:"apiKey"`
MutualSSL mutualSSL `toml:"mutualSSL"`
}
type internalKey struct {
Enabled bool
Expand Down
60 changes: 52 additions & 8 deletions adapter/internal/discovery/xds/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,17 +280,36 @@ func GenerateEnvoyResoucesForGateway(gatewayName string) ([]types.Resource,
// do nothing if the gateway is not found in the envoyInternalAPI
continue
}
removeJWTRequirements := false
if !envoyInternalAPI.adapterInternalAPI.GetDisableAuthentications() {
var jwtRequirements []*jwt.JwtRequirement
var authorizationHeader *string
var sendTokenToUpstream *bool
var apiKeyHeader *string
var apiKeyQueryParam *string
sendApikeyToUpstream := false
config := config.ReadConfigs()
if envoyInternalAPI.adapterInternalAPI != nil && envoyInternalAPI.adapterInternalAPI.GetResources() != nil {
for _, resource := range envoyInternalAPI.adapterInternalAPI.GetResources() {
if resource.GetMethod() != nil {
for _, method := range resource.GetMethod() {
if method.GetAuthentication() != nil {
if !method.GetAuthentication().Disabled && method.GetAuthentication().Oauth2 != nil {
authorizationHeader = &method.GetAuthentication().Oauth2.Header
sendTokenToUpstream = &method.GetAuthentication().Oauth2.SendTokenToUpstream
if !method.GetAuthentication().Disabled {
if method.GetAuthentication().Oauth2 != nil {
authorizationHeader = &method.GetAuthentication().Oauth2.Header
sendTokenToUpstream = &method.GetAuthentication().Oauth2.SendTokenToUpstream
}
if method.GetAuthentication().APIKey != nil && len(method.GetAuthentication().APIKey) > 0 {
for _, apiKeyConfig := range method.GetAuthentication().APIKey {
if apiKeyConfig.In == "Header" {
apiKeyHeader = &apiKeyConfig.Name
sendApikeyToUpstream = sendApikeyToUpstream || apiKeyConfig.SendTokenToUpstream
} else if apiKeyConfig.In == "Query" {
apiKeyQueryParam = &apiKeyConfig.Name
sendApikeyToUpstream = sendApikeyToUpstream || apiKeyConfig.SendTokenToUpstream
}
}
}
break
}
}
Expand All @@ -309,16 +328,41 @@ func GenerateEnvoyResoucesForGateway(gatewayName string) ([]types.Resource,
jwtProviderMap[key] = value
}
if jwtRequirement != nil {
jwtRequirementMap[envoyInternalAPI.adapterInternalAPI.UUID] = jwtRequirement
jwtRequirements = append(jwtRequirements, jwtRequirement...)
}
} else {
jwtRequirements := oasParser.GetJWTRequirements(envoyInternalAPI.adapterInternalAPI, orgwizeJWTProviders[organizationID])
if jwtRequirements != nil {
jwtRequirementMap[envoyInternalAPI.adapterInternalAPI.UUID] = jwtRequirements
jwtRequirement := oasParser.GetJWTRequirements(envoyInternalAPI.adapterInternalAPI, orgwizeJWTProviders[organizationID])
if jwtRequirement != nil {
jwtRequirements = append(jwtRequirements, jwtRequirement...)
}
}
logger.LoggerAPKOperator.Infof("API Key header is enabled for the API: %v", apiKeyHeader)
logger.LoggerAPKOperator.Infof("API Key query param is enabled for the API: %v", apiKeyQueryParam)
if config.Enforcer.Security.APIkey.Enabled && (apiKeyHeader != nil || apiKeyQueryParam != nil) {
apiKeyProviders, apiKeyClusters, apiKeyAddress, apiKeyRequirements, err := oasParser.GenerateAPIKeyProviders(envoyInternalAPI.adapterInternalAPI, apiKeyHeader, apiKeyQueryParam, &sendApikeyToUpstream)
if err != nil {
logger.LoggerXds.ErrorC(logging.PrintError(logging.Error2302, logging.MAJOR, "Error generating API Key Providers: %v", err))
}
logger.LoggerAPKOperator.Infof("API Key Providers: %+v", apiKeyProviders)
logger.LoggerAPKOperator.Infof("API Key Clusters: %+v", apiKeyClusters)
clusterArray = append(clusterArray, apiKeyClusters...)
endpointArray = append(endpointArray, apiKeyAddress...)
for key, value := range apiKeyProviders {
jwtProviderMap[key] = value
}
if apiKeyRequirements != nil {
jwtRequirements = append(jwtRequirements, apiKeyRequirements...)
}
}
if len(jwtRequirements) > 0 {
jwtRequirementMap[envoyInternalAPI.adapterInternalAPI.UUID] = &jwt.JwtRequirement{RequiresType: &jwt.JwtRequirement_RequiresAny{RequiresAny: &jwt.JwtRequirementOrList{Requirements: append(jwtRequirements, &jwt.JwtRequirement{
RequiresType: &jwt.JwtRequirement_AllowMissingOrFailed{},
})}}}
} else {
removeJWTRequirements = true
}
}
if envoyInternalAPI.adapterInternalAPI.RemoveJWTRequirements {
if removeJWTRequirements {
for _, route := range envoyInternalAPI.routes {
delete(route.TypedPerFilterConfig, envoyconf.EnvoyJWT)
}
Expand Down
5 changes: 5 additions & 0 deletions adapter/internal/logging/logging_constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ const (
Error2249 = 2249
Error2250 = 2250
Error2301 = 2301
Error2302 = 2302
)

// Error Log RateLimiter callbacks(2300-2399) Config Constants
Expand Down Expand Up @@ -423,4 +424,8 @@ var Mapper = map[int]logging.ErrorDetails{
ErrorCode: Error2301,
Message: "Error while generating JWTProviders",
},
Error2302: {
ErrorCode: Error2302,
Message: "Error while generating APIKey JWTProvider",
},
}
99 changes: 71 additions & 28 deletions adapter/internal/oasparser/config_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package oasparser

import (
"fmt"
"io/ioutil"
"strconv"
"strings"
"time"
Expand All @@ -32,9 +34,9 @@ import (
"github.com/wso2/apk/adapter/config"
logger "github.com/wso2/apk/adapter/internal/loggers"
"github.com/wso2/apk/adapter/internal/logging"
"github.com/wso2/apk/adapter/internal/oasparser/envoyconf"
envoy "github.com/wso2/apk/adapter/internal/oasparser/envoyconf"
"github.com/wso2/apk/adapter/internal/oasparser/model"
"github.com/wso2/apk/adapter/internal/operator/utils"
"github.com/wso2/apk/adapter/pkg/discovery/api/wso2/discovery/api"
"github.com/wso2/apk/common-go-libs/apis/dp/v1alpha1"
"google.golang.org/protobuf/types/known/anypb"
Expand Down Expand Up @@ -454,7 +456,7 @@ func generateRPCEndpointSecurity(inputEndpointSecurity []*model.EndpointSecurity

// GetJWTRequirements returns the jwt requirements for the resource

func GetJWTRequirements(adapterAPI *model.AdapterInternalAPI, jwtIssuers map[string]*v1alpha1.ResolvedJWTIssuer) *jwt.JwtRequirement {
func GetJWTRequirements(adapterAPI *model.AdapterInternalAPI, jwtIssuers map[string]*v1alpha1.ResolvedJWTIssuer) []*jwt.JwtRequirement {
var selectedIssuers []string
for issuserName, jwtIssuer := range jwtIssuers {
if contains(jwtIssuer.Environments, "*") {
Expand All @@ -468,34 +470,22 @@ func GetJWTRequirements(adapterAPI *model.AdapterInternalAPI, jwtIssuers map[str
}

// GetAPILevelJWTRequirements returns the jwt requirements for the resource
func GetAPILevelJWTRequirements(adapterAPI *model.AdapterInternalAPI, selectedIssuers []string) *jwt.JwtRequirement {
func GetAPILevelJWTRequirements(adapterAPI *model.AdapterInternalAPI, selectedIssuers []string) []*jwt.JwtRequirement {
var requirements []*jwt.JwtRequirement
if len(selectedIssuers) >= 1 {
return &jwt.JwtRequirement{
RequiresType: &jwt.JwtRequirement_RequiresAny{
RequiresAny: &jwt.JwtRequirementOrList{
Requirements: func() []*jwt.JwtRequirement {
var requirements []*jwt.JwtRequirement
for _, issuer := range selectedIssuers {
requirements = append(requirements, &jwt.JwtRequirement{
RequiresType: &jwt.JwtRequirement_ProviderName{
ProviderName: issuer,
},
})
}
requirements = append(requirements, &jwt.JwtRequirement{
RequiresType: &jwt.JwtRequirement_AllowMissingOrFailed{},
})
return requirements
}(),
for _, issuer := range selectedIssuers {
requirements = append(requirements, &jwt.JwtRequirement{
RequiresType: &jwt.JwtRequirement_ProviderName{
ProviderName: issuer,
},
}}
})
}
}
adapterAPI.RemoveJWTRequirements = true
return nil
return requirements
}

// GenerateAPILevelJWTPRoviders generates the jwt provider for the resource
func GenerateAPILevelJWTPRoviders(jwtIssuers map[string]*v1alpha1.ResolvedJWTIssuer, adapterAPI *model.AdapterInternalAPI, authorizationHeader *string, sendTokenToUpStream *bool) (map[string]*jwt.JwtProvider, []*clusterv3.Cluster, []*corev3.Address, *jwt.JwtRequirement, error) {
func GenerateAPILevelJWTPRoviders(jwtIssuers map[string]*v1alpha1.ResolvedJWTIssuer, adapterAPI *model.AdapterInternalAPI, authorizationHeader *string, sendTokenToUpStream *bool) (map[string]*jwt.JwtProvider, []*clusterv3.Cluster, []*corev3.Address, []*jwt.JwtRequirement, error) {
jwtProviders := map[string]*jwt.JwtProvider{}
var clusters []*clusterv3.Cluster
var addresses []*corev3.Address
Expand All @@ -511,7 +501,7 @@ func GenerateAPILevelJWTPRoviders(jwtIssuers map[string]*v1alpha1.ResolvedJWTIss
seleced = true
}
if seleced {
provider, cluster, address, err := getjwtAuthFilters(jwtIssuer, providerName)
provider, cluster, address, err := getjwtAuthFilters(jwtIssuer, providerName, nil)
if err != nil {
return nil, nil, nil, nil, err
}
Expand All @@ -530,6 +520,45 @@ func GenerateAPILevelJWTPRoviders(jwtIssuers map[string]*v1alpha1.ResolvedJWTIss
return jwtProviders, clusters, addresses, requirements, nil
}

// GenerateAPIKeyProviders generates the api key provider for the resource
func GenerateAPIKeyProviders(adapterAPI *model.AdapterInternalAPI, apiKeyHeader *string, apiKeyQueryParam *string, sendApikeyToUpstream *bool) (map[string]*jwt.JwtProvider, []*clusterv3.Cluster, []*corev3.Address, []*jwt.JwtRequirement, error) {
config := config.ReadConfigs()
providerName := adapterAPI.UUID + "-apikey"
certificate, err := LoadCertificate(config.Enforcer.Security.APIkey.CertificateFilePath)
if err != nil {
return nil, nil, nil, nil, err
}
jwkCertificateContent := utils.ConvertPemCertificatetoJWK(*certificate)
var clusters []*clusterv3.Cluster
var addresses []*corev3.Address
jwtIssuer := &v1alpha1.ResolvedJWTIssuer{
Name: providerName,
Issuer: config.Enforcer.Security.APIkey.Issuer,
Organization: adapterAPI.OrganizationID,
SignatureValidation: v1alpha1.ResolvedSignatureValidation{Certificate: &v1alpha1.ResolvedTLSConfig{ResolvedCertificate: jwkCertificateContent}},
}
keyType := "apikey"
provider, cluster, address, err := getjwtAuthFilters(jwtIssuer, providerName, &keyType)
if err != nil {
return nil, nil, nil, nil, err
}
if apiKeyHeader != nil {
provider.FromHeaders = []*jwt.JwtHeader{{Name: *apiKeyHeader}}
}
if apiKeyQueryParam != nil {
provider.FromParams = append(provider.FromParams, *apiKeyQueryParam)
}
if sendApikeyToUpstream != nil {
provider.Forward = *sendApikeyToUpstream
}
jwtProviders := map[string]*jwt.JwtProvider{}
jwtProviders[providerName] = provider
clusters = append(clusters, cluster...)
addresses = append(addresses, address...)
requirements := GetAPILevelJWTRequirements(adapterAPI, []string{providerName})
return jwtProviders, clusters, addresses, requirements, nil
}

// GenerateJWTPRoviderv3 generates the jwt provider for the resource
func GenerateJWTPRoviderv3(jwtProviderMap map[string]map[string]*v1alpha1.ResolvedJWTIssuer) (map[string]*jwt.JwtProvider, []*clusterv3.Cluster, []*corev3.Address, error) {
jwtProviders := map[string]*jwt.JwtProvider{}
Expand All @@ -538,7 +567,7 @@ func GenerateJWTPRoviderv3(jwtProviderMap map[string]map[string]*v1alpha1.Resolv

for _, orgwizeJWTProviders := range jwtProviderMap {
for issuerMappingName, jwtIssuer := range orgwizeJWTProviders {
provider, cluster, address, err := getjwtAuthFilters(jwtIssuer, issuerMappingName)
provider, cluster, address, err := getjwtAuthFilters(jwtIssuer, issuerMappingName, nil)
if err != nil {
return nil, nil, nil, err
}
Expand All @@ -559,7 +588,7 @@ func contains(arr []string, str string) bool {
}
return false
}
func getjwtAuthFilters(tokenIssuer *v1alpha1.ResolvedJWTIssuer, issuerName string) (*jwt.JwtProvider, []*clusterv3.Cluster, []*corev3.Address, error) {
func getjwtAuthFilters(tokenIssuer *v1alpha1.ResolvedJWTIssuer, issuerName string, keyType *string) (*jwt.JwtProvider, []*clusterv3.Cluster, []*corev3.Address, error) {
conf := config.ReadConfigs()

jwksClusters := make([]*clusterv3.Cluster, 0)
Expand All @@ -569,6 +598,10 @@ func getjwtAuthFilters(tokenIssuer *v1alpha1.ResolvedJWTIssuer, issuerName strin
FailedStatusInMetadata: tokenIssuer.Issuer + "-failed",
PayloadInMetadata: tokenIssuer.Issuer + "-payload",
}
if keyType != nil {
jwtProvider.FailedStatusInMetadata = *keyType + "-failed"
jwtProvider.PayloadInMetadata = *keyType + "-payload"
}
if conf.Enforcer.Cache.Enabled {
jwtProvider.JwtCacheConfig = &jwt.JwtCacheConfig{JwtCacheSize: uint32(conf.Enforcer.Cache.MaximumSize)}
}
Expand Down Expand Up @@ -636,9 +669,19 @@ func GetJWTFilter(jwtRequirement map[string]*jwt.JwtRequirement, jwtProviders ma
logger.LoggerOasparser.ErrorC(logging.PrintError(logging.Error2250, logging.CRITICAL, "Failed to parse JWTAuthentication %v", err.Error()))
}
return &hcmv3.HttpFilter{
Name: envoyconf.EnvoyJWT,
Name: envoy.EnvoyJWT,
ConfigType: &hcmv3.HttpFilter_TypedConfig{
TypedConfig: typedConfig,
},
}, nil
}

// LoadCertificate loads an x509 certificate from a file path
func LoadCertificate(path string) (*string, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read certificate file: %w", err)
}
cert := string(data)
return &cert, nil
}
1 change: 0 additions & 1 deletion adapter/internal/oasparser/model/adapter_internal_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ type AdapterInternalAPI struct {
AIProvider InternalAIProvider
AIModelBasedRoundRobin InternalModelBasedRoundRobin
HTTPRouteIDs []string
RemoveJWTRequirements bool
}

// InternalModelBasedRoundRobin holds the model based round robin configurations
Expand Down
Loading

0 comments on commit d061a66

Please sign in to comment.