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

ExtAuth handler #1258

Merged
merged 43 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b191555
AuthorizationPolicy processor for ExtAuth
barchw Aug 19, 2024
4ffbcff
consolidate pkg
barchw Aug 20, 2024
c36c4c6
Revert "consolidate pkg"
barchw Aug 20, 2024
0a49b30
ra
barchw Aug 20, 2024
c8197c1
Merge from main
barchw Aug 20, 2024
ec57862
Final touches before integration tests
barchw Aug 26, 2024
1d8fa78
Add int tests
barchw Aug 26, 2024
810a50a
Merge branch 'main' of https://github.com/kyma-project/api-gateway in…
barchw Aug 26, 2024
a01f011
Fix withFrom
barchw Aug 26, 2024
12a3231
Add integration tests
barchw Aug 26, 2024
c65db21
Update oauth2-server-mock.yaml
barchw Aug 27, 2024
3db9d9d
Fix regex
barchw Aug 27, 2024
4931ea2
Merge branch 'main' into ext-auth
barchw Aug 27, 2024
37deb79
Remove requirement for more than one rule
barchw Aug 27, 2024
87c8a24
Add error log
barchw Aug 27, 2024
16053fb
Revert old features
barchw Aug 27, 2024
9af9400
Add validation
barchw Aug 27, 2024
43e84bf
Revert revert tests
barchw Aug 27, 2024
2f49b0b
Adapt check to test ALLOW creation
barchw Aug 27, 2024
0846f30
Change path
barchw Aug 27, 2024
f598006
Fix bad regex
barchw Aug 28, 2024
6b156dd
Fix init for custom label
barchw Aug 28, 2024
7144875
Docs
barchw Aug 28, 2024
9db5930
Add restricions doc
barchw Aug 28, 2024
cf2e316
Add EOF
barchw Aug 28, 2024
9b17054
Merge branch 'main' into ext-auth
kolodziejczak Sep 3, 2024
0784b27
review suggestions
kolodziejczak Sep 5, 2024
5df856a
Apply suggestions from code review
triffer Sep 5, 2024
db9c1b2
Apply documentation review remarks
triffer Sep 5, 2024
9ddf19f
Split code for external authorizer validation
triffer Sep 5, 2024
4c44cce
Export CorsPolicyBuilder, because it is used by exported function.
triffer Sep 5, 2024
2f9b628
Introduce hook to update Istio CR ext auth config as part of the test…
triffer Sep 6, 2024
19e58c6
Introduce hook to update Istio CR ext auth config as part of the test…
triffer Sep 6, 2024
a4bba09
Introduce hook to update Istio CR ext auth config as part of the test…
triffer Sep 6, 2024
bd059ba
Store simplified ext-auth in v1beta1 and revert CRD validation
barchw Sep 9, 2024
7c51270
Fix merge-conflicts
barchw Sep 9, 2024
ea2ed13
Fix lint error
barchw Sep 9, 2024
6a37452
Store all as annotation
barchw Sep 9, 2024
bb00e5b
Remove parentheses
barchw Sep 9, 2024
3c30e17
Fix lint issue
barchw Sep 10, 2024
3e96f1e
Add yet another unit test
barchw Sep 10, 2024
1a7848e
Merge branch 'main' into ext-auth
barchw Sep 10, 2024
a1b2488
Update docs/release-notes/2.6.0.md
barchw Sep 10, 2024
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
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,11 @@ test: manifests generate fmt vet envtest ## Generate manifests and run tests.
test-integration: test-integration-v2alpha1 test-integration-ory test-integration-istio test-integration-gateway

.PHONY: test-integration-v2alpha1
test-integration-v2alpha1: generate fmt vet
test-integration-v2alpha1: generate fmt vet ## Run API Gateway integration tests with v2alpha1 API.
kubectl create ns ext-auth --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.23/samples/extauthz/ext-authz.yaml -n ext-auth
source ./tests/integration/env_vars.sh && go test -timeout 1h ./tests/integration -v -race -run TestV2alpha1
kubectl delete ns ext-auth

.PHONY: test-integration-ory
test-integration-ory: generate fmt vet
Expand Down
1 change: 0 additions & 1 deletion apis/gateway/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 44 additions & 15 deletions apis/gateway/v2alpha1/apirule_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package v2alpha1

import (
"encoding/json"
"k8s.io/apimachinery/pkg/runtime"
"time"

"github.com/kyma-project/api-gateway/apis/gateway/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/conversion"
)
Expand All @@ -32,6 +32,8 @@ func convertMap(m map[v1beta1.StatusCode]State) map[State]v1beta1.StatusCode {
// The 2 => 1 map is generated automatically based on 1 => 2 map
var alpha1to1beta1statusConversionMap = convertMap(beta1toV2alpha1StatusConversionMap)

const v2alpha1RulesAnnotationKey = "gateway.kyma-project.io/v2alpha1-rules"

// ConvertTo Converts this ApiRule (v2alpha1) to the Hub version (v1beta1)
func (apiRuleV2Alpha1 *APIRule) ConvertTo(hub conversion.Hub) error {
apiRuleBeta1 := hub.(*v1beta1.APIRule)
Expand Down Expand Up @@ -89,36 +91,55 @@ func (apiRuleV2Alpha1 *APIRule) ConvertTo(hub conversion.Hub) error {
}

if len(apiRuleV2Alpha1.Spec.Rules) > 0 {
marshaledApiRules, err := json.Marshal(apiRuleV2Alpha1.Spec.Rules)
if err != nil {
return err
}
if len(apiRuleBeta1.Annotations) == 0 {
apiRuleBeta1.Annotations = make(map[string]string)
}
apiRuleBeta1.Annotations[v2alpha1RulesAnnotationKey] = string(marshaledApiRules)

apiRuleBeta1.Spec.Rules = []v1beta1.Rule{}
for _, ruleV1Alpha2 := range apiRuleV2Alpha1.Spec.Rules {
for _, ruleV2Alpha1 := range apiRuleV2Alpha1.Spec.Rules {
ruleBeta1 := v1beta1.Rule{}
err = convertOverJson(ruleV1Alpha2, &ruleBeta1)
err = convertOverJson(ruleV2Alpha1, &ruleBeta1)
if err != nil {
return err
}
// No Auth
if ruleV1Alpha2.NoAuth != nil && *ruleV1Alpha2.NoAuth {

// ExtAuth
if ruleV2Alpha1.ExtAuth != nil {
ruleBeta1.AccessStrategies = append(ruleBeta1.AccessStrategies, &v1beta1.Authenticator{
Handler: &v1beta1.Handler{
Name: "ext-auth",
},
})
}

// NoAuth
if ruleV2Alpha1.NoAuth != nil && *ruleV2Alpha1.NoAuth {
ruleBeta1.AccessStrategies = append(ruleBeta1.AccessStrategies, &v1beta1.Authenticator{
Handler: &v1beta1.Handler{
Name: v1beta1.AccessStrategyNoAuth,
},
})
}
// JWT
if ruleV1Alpha2.Jwt != nil {
if ruleV2Alpha1.Jwt != nil {
ruleBeta1.AccessStrategies = append(ruleBeta1.AccessStrategies, &v1beta1.Authenticator{
Handler: &v1beta1.Handler{
Name: v1beta1.AccessStrategyJwt,
Config: &runtime.RawExtension{Object: ruleV1Alpha2.Jwt},
Config: &runtime.RawExtension{Object: ruleV2Alpha1.Jwt},
},
})
}

// Mutators
if ruleV1Alpha2.Request != nil {
if ruleV1Alpha2.Request.Cookies != nil {
if ruleV2Alpha1.Request != nil {
if ruleV2Alpha1.Request.Cookies != nil {
var config runtime.RawExtension
err := convertOverJson(ruleV1Alpha2.Request.Cookies, &config)
err := convertOverJson(ruleV2Alpha1.Request.Cookies, &config)
if err != nil {
return err
}
Expand All @@ -130,9 +151,9 @@ func (apiRuleV2Alpha1 *APIRule) ConvertTo(hub conversion.Hub) error {
})
}

if ruleV1Alpha2.Request.Headers != nil {
if ruleV2Alpha1.Request.Headers != nil {
var config runtime.RawExtension
err := convertOverJson(ruleV1Alpha2.Request.Headers, &config)
err := convertOverJson(ruleV2Alpha1.Request.Headers, &config)
if err != nil {
return err
}
Expand All @@ -148,7 +169,6 @@ func (apiRuleV2Alpha1 *APIRule) ConvertTo(hub conversion.Hub) error {
apiRuleBeta1.Spec.Rules = append(apiRuleBeta1.Spec.Rules, ruleBeta1)
}
}

return nil
}

Expand Down Expand Up @@ -215,7 +235,15 @@ func (apiRuleV2Alpha1 *APIRule) ConvertFrom(hub conversion.Hub) error {
*apiRuleV2Alpha1.Spec.Hosts[0] = Host(*apiRuleBeta1.Spec.Host)
}

if len(apiRuleBeta1.Spec.Rules) > 0 {
if annotation, ok := apiRuleBeta1.Annotations[v2alpha1RulesAnnotationKey]; ok {
var v2alpha1Rules []Rule
err := json.Unmarshal([]byte(annotation), &v2alpha1Rules)
if err != nil {
return err
}

apiRuleV2Alpha1.Spec.Rules = v2alpha1Rules
} else if len(apiRuleBeta1.Spec.Rules) > 0 {
apiRuleV2Alpha1.Spec.Rules = []Rule{}
for _, ruleBeta1 := range apiRuleBeta1.Spec.Rules {
ruleV1Alpha2 := Rule{}
Expand Down Expand Up @@ -268,6 +296,7 @@ func (apiRuleV2Alpha1 *APIRule) ConvertFrom(hub conversion.Hub) error {
}
apiRuleV2Alpha1.Spec.Rules = append(apiRuleV2Alpha1.Spec.Rules, ruleV1Alpha2)
}

}

return nil
Expand All @@ -292,7 +321,7 @@ func isFullConversionPossible(apiRule *v1beta1.APIRule) (bool, error) {
for _, rule := range apiRule.Spec.Rules {
for _, accessStrategy := range rule.AccessStrategies {

if accessStrategy.Handler.Name == v1beta1.AccessStrategyNoAuth {
if accessStrategy.Handler.Name == v1beta1.AccessStrategyNoAuth || accessStrategy.Handler.Name == "ext-auth" {
continue
}

Expand Down
15 changes: 14 additions & 1 deletion apis/gateway/v2alpha1/apirule_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ type Service struct {
}

// Rule .
// +kubebuilder:validation:XValidation:rule="has(self.jwt) ? !has(self.noAuth) || self.noAuth == false : has(self.noAuth) && self.noAuth == true",message="either jwt is configured or noAuth must be set to true in a rule"
// +kubebuilder:validation:XValidation:rule="((has(self.extAuth)?1:0)+(has(self.jwt)?1:0)+((has(self.noAuth)&&self.noAuth==true)?1:0))==1",message="One of the following fields must be set: noAuth, jwt, extAuth"
type Rule struct {
// Specifies the path of the exposed service.
// +kubebuilder:validation:Pattern=^([0-9a-zA-Z./*()?!\\_-]+)
Expand All @@ -134,6 +134,9 @@ type Rule struct {
// Specifies the Istio JWT access strategy.
// +optional
Jwt *JwtConfig `json:"jwt,omitempty"`
// Specifies external authorization configuration.
// +optional
ExtAuth *ExtAuth `json:"extAuth,omitempty"`
// +optional
Timeout *Timeout `json:"timeout,omitempty"`
// Request allows modifying the request before it is forwarded to the service.
Expand Down Expand Up @@ -194,6 +197,16 @@ type JwtHeader struct {
Prefix string `json:"prefix,omitempty"`
}

// ExtAuth contains configuration for paths that use external authorization.
type ExtAuth struct {
// Specifies the name of the external authorization handler.
// +optional
ExternalAuthorizers []string `json:"authorizers"`
// Specifies JWT configuration for the external authorization handler.
// +optional
Restrictions *JwtConfig `json:"restrictions,omitempty"`
}

// Timeout for HTTP requests in seconds. The timeout can be configured up to 3900 seconds (65 minutes).
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=3900
Expand Down
124 changes: 124 additions & 0 deletions apis/gateway/v2alpha1/extauth_conversion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package v2alpha1_test

import (
apirulev1beta1 "github.com/kyma-project/api-gateway/apis/gateway/v1beta1"
apirulev2alpha1 "github.com/kyma-project/api-gateway/apis/gateway/v2alpha1"
v2alpha1 "github.com/kyma-project/api-gateway/internal/builders/builders_test/v2alpha1_test"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

const annotationKey = "gateway.kyma-project.io/v2alpha1-rules"

var dummyExtAuthRule = v2alpha1.NewRuleBuilder().
WithPath("/test").
WithMethods("GET").
WithExtAuth(v2alpha1.NewExtAuthBuilder().
WithAuthorizers("test-authorizer").
WithRestriction(&apirulev2alpha1.JwtConfig{
Authentications: []*apirulev2alpha1.JwtAuthentication{
{
Issuer: "test-issuer",
JwksUri: "test-jwks-uri",
FromHeaders: []*apirulev2alpha1.JwtHeader{
{
Name: "test-header",
Prefix: "test-prefix",
},
},
FromParams: []string{"test-param"},
},
},
Authorizations: nil,
}).
Build()).
Build()

var _ = Describe("ExtAuthStorage", func() {
It("Should store extAuth in v1beta1 through annotation and a rule with only handler name set", func() {
// given
v2alpha1APIRule := v2alpha1.NewAPIRuleBuilderWithDummyData().WithRules(dummyExtAuthRule).Build()

// when
var betaConverted apirulev1beta1.APIRule
err := v2alpha1APIRule.ConvertTo(&betaConverted)
Expect(err).ToNot(HaveOccurred())

//then
annotations := betaConverted.GetAnnotations()
Expect(annotations).To(HaveKey(annotationKey))
Expect(betaConverted.Spec.Rules).To(HaveLen(1))
Expect(betaConverted.Spec.Rules[0].Path).To(Equal("/test"))
Expect(betaConverted.Spec.Rules[0].Methods).To(BeEquivalentTo([]apirulev1beta1.HttpMethod{"GET"}))
Expect(betaConverted.Spec.Rules[0].AccessStrategies).To(HaveLen(1))
Expect(betaConverted.Spec.Rules[0].AccessStrategies[0].Handler.Name).To(BeEquivalentTo("ext-auth"))
Expect(betaConverted.Spec.Rules[0].AccessStrategies[0].Config).To(BeNil())
})
})

var _ = Describe("ExtAuthConversion", func() {

DescribeTable("Should convert back and forth correctly with ExtAuth set", func(expectedRules []*apirulev2alpha1.Rule) {
// given
v2alpha1APIRule := v2alpha1.NewAPIRuleBuilderWithDummyData().WithRules(expectedRules...).Build()
var betaConverted apirulev1beta1.APIRule
err := v2alpha1APIRule.ConvertTo(&betaConverted)
Expect(err).ToNot(HaveOccurred())

// when
var v2alpha1ConvertedRule apirulev2alpha1.APIRule
err = v2alpha1ConvertedRule.ConvertFrom(&betaConverted)

// then
Expect(err).ToNot(HaveOccurred())
Expect(v2alpha1ConvertedRule.Spec.Rules).To(HaveLen(len(expectedRules)))
for i, rule := range v2alpha1ConvertedRule.Spec.Rules {
Expect(rule.Path).To(Equal(expectedRules[i].Path))
Expect(rule.Methods).To(BeEquivalentTo(expectedRules[i].Methods))
Expect(rule.Service).To(BeEquivalentTo(expectedRules[i].Service))
Expect(rule.NoAuth).To(Equal(expectedRules[i].NoAuth))
Expect(rule.Jwt != nil).To(Equal(expectedRules[i].Jwt != nil))
if rule.Jwt != nil {
Expect(rule.Jwt.Authorizations).To(BeEquivalentTo(expectedRules[i].Jwt.Authorizations))
Expect(rule.Jwt.Authentications).To(BeEquivalentTo(expectedRules[i].Jwt.Authentications))
}
Expect(rule.ExtAuth != nil).To(Equal(expectedRules[i].ExtAuth != nil))
if rule.ExtAuth != nil {
Expect(rule.ExtAuth.ExternalAuthorizers).To(BeEquivalentTo(expectedRules[i].ExtAuth.ExternalAuthorizers))
Expect(rule.ExtAuth.Restrictions != nil).To(Equal(expectedRules[i].ExtAuth.Restrictions != nil))
if rule.ExtAuth.Restrictions != nil {
Expect(rule.ExtAuth.Restrictions.Authentications).To(BeEquivalentTo(expectedRules[i].ExtAuth.Restrictions.Authentications))
Expect(rule.ExtAuth.Restrictions.Authorizations).To(BeEquivalentTo(expectedRules[i].ExtAuth.Restrictions.Authorizations))
}
}
}
},
Entry("Should convert APIRule with no ExtAuth", []*apirulev2alpha1.Rule{}),
Entry("Should convert APIRule with only ExtAuth", []*apirulev2alpha1.Rule{dummyExtAuthRule}),
Entry("Should preserve order of rules when ExtAuth is in the middle", []*apirulev2alpha1.Rule{
v2alpha1.NewRuleBuilder().
WithPath("/first").
NoAuth().
Build(),
dummyExtAuthRule,
v2alpha1.NewRuleBuilder().
WithPath("/third").
NoAuth().
Build(),
}),
Entry("Should preserve order of rules when ExtAuth is at the end", []*apirulev2alpha1.Rule{
v2alpha1.NewRuleBuilder().
WithPath("/first").
NoAuth().
Build(),
dummyExtAuthRule,
}),
Entry("Should preserve order of rules when ExtAuth is at the beginning", []*apirulev2alpha1.Rule{
dummyExtAuthRule,
v2alpha1.NewRuleBuilder().
WithPath("/second").
NoAuth().
Build(),
}),
)
})
19 changes: 12 additions & 7 deletions apis/gateway/v2alpha1/jwt_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package v2alpha1

import (
"encoding/json"

"errors"
"github.com/kyma-project/api-gateway/apis/gateway/v1beta1"
"github.com/kyma-project/api-gateway/internal/types/ory"
)
Expand Down Expand Up @@ -35,19 +35,24 @@ func convertOryJwtAccessStrategy(accessStrategy *v1beta1.Authenticator) (*v1beta
}

func convertIstioJwtAccessStrategy(accessStrategy *v1beta1.Authenticator) (*v1beta1.JwtConfig, error) {
var jwtConfig *v1beta1.JwtConfig

if accessStrategy.Config.Object != nil {
jwtConfig = accessStrategy.Config.Object.(*v1beta1.JwtConfig)
} else if accessStrategy.Config.Raw != nil {
jwtConfig = &v1beta1.JwtConfig{}
err := json.Unmarshal(accessStrategy.Config.Raw, jwtConfig)
jwtConfig, ok := accessStrategy.Config.Object.(*v1beta1.JwtConfig)
if ok {
return jwtConfig, nil
}
}

if accessStrategy.Config.Raw != nil {
var jwtConfig v1beta1.JwtConfig
err := json.Unmarshal(accessStrategy.Config.Raw, &jwtConfig)
if err != nil {
return nil, err
}
return &jwtConfig, nil
}

return jwtConfig, nil
return nil, errors.New("no raw config to convert")
}

func isConvertibleJwtConfig(accessStrategy *v1beta1.Authenticator) (bool, error) {
Expand Down
Loading
Loading