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

Implement controller and translator for KongVault #5384

Merged
merged 9 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ webhooks:
- UPDATE
resources:
- kongconsumers
- kongconsumergroups
- kongplugins
- kongclusterplugins
- kongingresses
- kongvaults
scope: '*'
- apiGroups:
- ""
Expand Down
1 change: 1 addition & 0 deletions config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ resources:
- bases/configuration.konghq.com_kongplugins.yaml
- bases/configuration.konghq.com_ingressclassparameterses.yaml
- bases/configuration.konghq.com_kongupstreampolicies.yaml
- bases/configuration.konghq.com_kongvaults.yaml
#+kubebuilder:scaffold:crdkustomizeresource

# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
Expand Down
16 changes: 16 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@ rules:
- get
- patch
- update
- apiGroups:
- configuration.konghq.com
resources:
- kongvaults
verbs:
- get
- list
- watch
- apiGroups:
- configuration.konghq.com
resources:
- kongvaults/status
verbs:
- get
- patch
- update
- apiGroups:
- configuration.konghq.com
resources:
Expand Down
1 change: 1 addition & 0 deletions docs/cli-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
| `--enable-controller-ingress-networkingv1` | `bool` | Enable the networking.k8s.io/v1 Ingress controller. | `true` |
| `--enable-controller-kong-service-facade` | `bool` | Enable the KongServiceFacade controller. | `true` |
| `--enable-controller-kong-upstream-policy` | `bool` | Enable the KongUpstreamPolicy controller. | `true` |
| `--enable-controller-kong-vault` | `bool` | Enable the KongVault controller. | `true` |
| `--enable-controller-kongclusterplugin` | `bool` | Enable the KongClusterPlugin controller. | `true` |
| `--enable-controller-kongconsumer` | `bool` | Enable the KongConsumer controller. | `true` |
| `--enable-controller-kongingress` | `bool` | Enable the KongIngress controller. | `true` |
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ require (
github.com/google/uuid v1.5.0
github.com/jpillora/backoff v1.0.0
github.com/kong/go-database-reconciler v1.1.0
github.com/kong/go-kong v0.48.0
github.com/kong/go-kong v0.49.0
github.com/kong/kubernetes-telemetry v0.1.3
github.com/kong/kubernetes-testing-framework v0.43.0
github.com/lithammer/dedent v1.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,8 @@ github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJw
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kong/go-database-reconciler v1.1.0 h1:USCdsAj/7eh9sOOfbnvsOe4jw5k4+FSTD3okcTLIVqQ=
github.com/kong/go-database-reconciler v1.1.0/go.mod h1:p8NvafqBSuMR9YNCOZ24aIeeajc145+biXpAaMExvpI=
github.com/kong/go-kong v0.48.0 h1:vK1OpoxO50qlKdwPfmx9ChvkTKRsoCCB3b3iHo1umLc=
github.com/kong/go-kong v0.48.0/go.mod h1:qH4CEFqT83ywmu1TlMZX09clQH4B8/dX88CtT/jdv/E=
github.com/kong/go-kong v0.49.0 h1:QtO0TtVPYQXjBJ/MNptvyodRNwubbtIkoNhQkh17Q1g=
github.com/kong/go-kong v0.49.0/go.mod h1:xDf1RfkaE/rAwNE1fS3XniFj/d2JmkEER2S9NDY12Yw=
github.com/kong/kubernetes-telemetry v0.1.3 h1:Hz2tkHGIIUqbn1x46QRDmmNjbEtJyxyOvHSPne3uPto=
github.com/kong/kubernetes-telemetry v0.1.3/go.mod h1:wB7o8dOKa5R396CyiU0sPa8am/g3c5DKd/qrn/Vmb+k=
github.com/kong/kubernetes-testing-framework v0.43.0 h1:Pjh4NMlwApFqi9RPW5NPHD3mOCA62pHKLLX4pWnHGXw=
Expand Down
1 change: 1 addition & 0 deletions hack/deploy-admission-controller.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ webhooks:
- kongplugins
- kongclusterplugins
- kongingresses
- kongvaults
- apiGroups:
- ''
apiVersions:
Expand Down
17 changes: 17 additions & 0 deletions hack/generators/controllers/networking/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,23 @@ var inputControllersNeeded = &typesNeeded{
AcceptsIngressClassNameAnnotation: true,
RBACVerbs: []string{"get", "list", "watch"},
},
typeNeeded{
Group: "configuration.konghq.com",
Version: "v1alpha1",
Kind: "KongVault",
PackageImportAlias: "kongv1alpha1",
PackageAlias: "KongV1Alpha1",
Package: kongv1alpha1,
Plural: "kongvaults",
CacheType: "KongVault",
NeedsStatusPermissions: true,
ConfigStatusNotificationsEnabled: true,
ProgrammedCondition: ProgrammedConditionConfiguration{
UpdatesEnabled: true,
},
AcceptsIngressClassNameAnnotation: true,
RBACVerbs: []string{"get", "list", "watch"},
},
}

var inputRBACPermissionsNeeded = &rbacsNeeded{
Expand Down
8 changes: 8 additions & 0 deletions internal/admission/adminapi_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ func (p DefaultAdminAPIServicesProvider) GetRoutesService() (kong.AbstractRouteS
return c.Routes, true
}

func (p DefaultAdminAPIServicesProvider) GetVaultsService() (kong.AbstractVaultService, bool) {
c, ok := p.designatedAdminAPIClient()
if !ok {
return nil, ok
}
return c.Vaults, true
}

func (p DefaultAdminAPIServicesProvider) designatedAdminAPIClient() (*kong.Client, bool) {
gwClients := p.gatewayClientsProvider.GatewayClients()
if len(gwClients) == 0 {
Expand Down
3 changes: 3 additions & 0 deletions internal/admission/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const (
ErrTextPluginConfigValidationFailed = "unable to validate plugin schema"
ErrTextPluginConfigViolatesSchema = "plugin failed schema validation: %s"
ErrTextPluginSecretConfigUnretrievable = "could not load secret plugin configuration"
ErrTextVaultConfigUnmarshalFailed = "failed to unmarshal validate configurations: %v"
randmonkey marked this conversation as resolved.
Show resolved Hide resolved
ErrTextVaultUnableToValidate = "unable to validate vault on Kong gateway"
ErrTextVaultConfigValidationResultInvalid = "vault configuration in invalid: %s"
)

const (
Expand Down
22 changes: 22 additions & 0 deletions internal/admission/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
)

Expand Down Expand Up @@ -96,6 +97,11 @@ var (
Version: kongv1.SchemeGroupVersion.Version,
Resource: "kongingresses",
}
kongVaultGVResource = metav1.GroupVersionResource{
Group: kongv1alpha1.SchemeGroupVersion.Group,
Version: kongv1alpha1.SchemeGroupVersion.Version,
Resource: "kongvaults",
}
secretGVResource = metav1.GroupVersionResource{
Group: corev1.SchemeGroupVersion.Group,
Version: corev1.SchemeGroupVersion.Version,
Expand Down Expand Up @@ -135,6 +141,8 @@ func (h RequestHandler) handleValidation(ctx context.Context, request admissionv
return h.handleHTTPRoute(ctx, request, responseBuilder)
case kongIngressGVResource:
return h.handleKongIngress(ctx, request, responseBuilder)
case kongVaultGVResource:
return h.handleKongVault(ctx, request, responseBuilder)
case serviceGVResource:
return h.handleService(ctx, request, responseBuilder)
case ingressGVResource:
Expand Down Expand Up @@ -426,3 +434,17 @@ func (h RequestHandler) handleIngress(ctx context.Context, request admissionv1.A

return responseBuilder.Allowed(ok).WithMessage(message).Build(), nil
}

func (h RequestHandler) handleKongVault(ctx context.Context, request admissionv1.AdmissionRequest, responseBuilder *ResponseBuilder) (*admissionv1.AdmissionResponse, error) {
kongVault := kongv1alpha1.KongVault{}
_, _, err := codecs.UniversalDeserializer().Decode(request.Object.Raw, nil, &kongVault)
if err != nil {
return nil, err
}
ok, message, err := h.Validator.ValidateVault(ctx, kongVault)
if err != nil {
return nil, err
}

return responseBuilder.Allowed(ok).WithMessage(message).Build(), nil
}
5 changes: 5 additions & 0 deletions internal/admission/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
)

Expand Down Expand Up @@ -77,6 +78,10 @@ func (v KongFakeValidator) ValidateIngress(_ context.Context, _ netv1.Ingress) (
return v.Result, v.Message, v.Error
}

func (v KongFakeValidator) ValidateVault(_ context.Context, _ kongv1alpha1.KongVault) (bool, string, error) {
return v.Result, v.Message, v.Error
}

func TestServeHTTPBasic(t *testing.T) {
assert := assert.New(t)
res := httptest.NewRecorder()
Expand Down
42 changes: 42 additions & 0 deletions internal/admission/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
)

Expand All @@ -35,6 +36,7 @@ type KongValidator interface {
ValidateConsumerGroup(ctx context.Context, consumerGroup kongv1beta1.KongConsumerGroup) (bool, string, error)
ValidatePlugin(ctx context.Context, plugin kongv1.KongPlugin, overrideSecrets []*corev1.Secret) (bool, string, error)
ValidateClusterPlugin(ctx context.Context, plugin kongv1.KongClusterPlugin, overrideSecrets []*corev1.Secret) (bool, string, error)
ValidateVault(ctx context.Context, vault kongv1alpha1.KongVault) (bool, string, error)
ValidateCredential(ctx context.Context, secret corev1.Secret) (bool, string)
ValidateGateway(ctx context.Context, gateway gatewayapi.Gateway) (bool, string, error)
ValidateHTTPRoute(ctx context.Context, httproute gatewayapi.HTTPRoute) (bool, string, error)
Expand All @@ -49,6 +51,7 @@ type AdminAPIServicesProvider interface {
GetConsumerGroupsService() (kong.AbstractConsumerGroupService, bool)
GetInfoService() (kong.AbstractInfoService, bool)
GetRoutesService() (kong.AbstractRouteService, bool)
GetVaultsService() (kong.AbstractVaultService, bool)
}

// ConsumerGetter is an interface for retrieving KongConsumers.
Expand Down Expand Up @@ -520,6 +523,30 @@ func (noOpRoutesValidator) Validate(_ context.Context, _ *kong.Route) (bool, str
return true, "", nil
}

func (validator KongHTTPValidator) ValidateVault(ctx context.Context, k8sKongVault kongv1alpha1.KongVault) (bool, string, error) {
// Ignore KongVaults that are being managed by another controller.
if !validator.ingressClassMatcher(&k8sKongVault.ObjectMeta, annotations.IngressClassKey, annotations.ExactClassMatch) {
return true, "", nil
}
config, err := kongstate.RawConfigToConfiguration(k8sKongVault.Spec.Config.Raw)
if err != nil {
return false, fmt.Sprintf(ErrTextVaultConfigUnmarshalFailed, err), nil
}
kongVault := kong.Vault{
Name: kong.String(k8sKongVault.Spec.Backend),
Prefix: kong.String(k8sKongVault.Spec.Prefix),
Description: kong.String(k8sKongVault.Spec.Description),
Config: config,
}
// TODO: /schemas/vaults/test does not check "unique" restraint on `prefix` field.
// We may need implement the uniqueness check of `prefix`.
randmonkey marked this conversation as resolved.
Show resolved Hide resolved
errText, err := validator.validateVaultAgainstGatewaySchema(ctx, kongVault)
if err != nil || errText != "" {
return false, errText, err
}
return true, "", nil
}

// -----------------------------------------------------------------------------
// KongHTTPValidator - Private Methods
// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -582,6 +609,21 @@ func (validator KongHTTPValidator) validatePluginAgainstGatewaySchema(ctx contex
return "", nil
}

func (validator KongHTTPValidator) validateVaultAgainstGatewaySchema(ctx context.Context, vault kong.Vault) (string, error) {
vaultService, hasClient := validator.AdminAPIServicesProvider.GetVaultsService()
if !hasClient {
return "", nil
}
isValid, msg, err := vaultService.Validate(ctx, &vault)
if err != nil {
return ErrTextVaultUnableToValidate, err
}
if !isValid {
return fmt.Sprintf(ErrTextVaultConfigValidationResultInvalid, msg), nil
}
return "", nil
}

type managerClientSecretGetter struct {
managerClient client.Client
}
Expand Down
96 changes: 96 additions & 0 deletions internal/admission/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
"github.com/kong/kubernetes-ingress-controller/v3/internal/util/builder"
kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
kongv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1alpha1"
kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
incubatorv1alpha1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/incubator/v1alpha1"
)
Expand Down Expand Up @@ -62,6 +63,7 @@ type fakeServicesProvider struct {
consumerGroupSvc kong.AbstractConsumerGroupService
infoSvc kong.AbstractInfoService
routeSvc kong.AbstractRouteService
vaultSvc kong.AbstractVaultService
}

func (f fakeServicesProvider) GetConsumersService() (kong.AbstractConsumerService, bool) {
Expand Down Expand Up @@ -99,6 +101,13 @@ func (f fakeServicesProvider) GetRoutesService() (kong.AbstractRouteService, boo
return nil, false
}

func (f fakeServicesProvider) GetVaultsService() (kong.AbstractVaultService, bool) {
if f.vaultSvc != nil {
return f.vaultSvc, true
}
return nil, false
}

func TestKongHTTPValidator_ValidatePlugin(t *testing.T) {
store, _ := store.NewFakeStore(store.FakeObjects{
Secrets: []*corev1.Secret{
Expand Down Expand Up @@ -1227,3 +1236,90 @@ func (f *fakeRouteSvc) Validate(context.Context, *kong.Route) (bool, string, err
}
return true, "", nil
}

func TestValidator_ValidateVault(t *testing.T) {
testCases := []struct {
name string
kongVault kongv1alpha1.KongVault
validateSvcFail bool
expectedOK bool
expectedMessage string
expectedError string
}{
{
name: "valid vault",
kongVault: kongv1alpha1.KongVault{
ObjectMeta: metav1.ObjectMeta{
Name: "vault-1",
},
Spec: kongv1alpha1.KongVaultSpec{
Backend: "env",
Prefix: "env-1",
},
},
expectedOK: true,
},
{
name: "vault with invalid(malformed) configuration",
kongVault: kongv1alpha1.KongVault{
ObjectMeta: metav1.ObjectMeta{
Name: "vault-1",
},
Spec: kongv1alpha1.KongVaultSpec{
Backend: "env",
Prefix: "env-1",
Config: apiextensionsv1.JSON{
Raw: []byte(`{{}`),
},
},
},
expectedOK: false,
expectedMessage: "failed to unmarshal validate configurations",
},
{
name: "vault with failure in validating service",
kongVault: kongv1alpha1.KongVault{
ObjectMeta: metav1.ObjectMeta{
Name: "vault-1",
},
Spec: kongv1alpha1.KongVaultSpec{
Backend: "env",
Prefix: "env-1",
},
},
validateSvcFail: true,
expectedOK: false,
expectedMessage: "something is wrong with the vault",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
validator := KongHTTPValidator{
AdminAPIServicesProvider: fakeServicesProvider{
vaultSvc: &fakeVaultSvc{
shouldFail: tc.validateSvcFail,
},
},
ingressClassMatcher: fakeClassMatcher,
Logger: logr.Discard(),
}
ok, msg, err := validator.ValidateVault(context.Background(), tc.kongVault)
require.NoError(t, err)
assert.Equal(t, tc.expectedOK, ok)
assert.Contains(t, msg, tc.expectedMessage)
})
}
}

type fakeVaultSvc struct {
kong.AbstractVaultService
shouldFail bool
}

func (s fakeVaultSvc) Validate(context.Context, *kong.Vault) (bool, string, error) {
if s.shouldFail {
return false, "something is wrong with the vault", nil
}
return true, "", nil
}