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 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
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,13 @@ Adding a new version? You'll need three changes:
- Added functionality to the `KongUpstreamPolicy` controller to properly set and
enforce `KongUpstreamPolicy` status.
[#5185](https://github.com/Kong/kubernetes-ingress-controller/pull/5185)
- Add CRD `KongVault` to reperesent a custom Kong vault for storing senstive
data used in plugin configurations.
- New CRD `KongVault` to reperesent a custom Kong vault for storing senstive
data used in plugin configurations. Now users can create a `KongVault` to
create a custom Kong vault and reference the values in configurations of
plugins. Reference of using Kong vaults: [Kong vault]
[#5354](https://github.com/Kong/kubernetes-ingress-controller/pull/5354)
[#5384](https://github.com/Kong/kubernetes-ingress-controller/pull/5384)


### Fixed

Expand Down Expand Up @@ -165,6 +169,8 @@ Adding a new version? You'll need three changes:
- `KongPlugin` and `KongClusterPlugin` now enforce `plugin` to be immutable.
[#5142](https://github.com/Kong/kubernetes-ingress-controller/pull/5142)

[Kong vault]: https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/

## [3.0.1]

> Release date: 2023-11-22
Expand Down
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
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 vault configuration: %v"
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:
// https://github.com/Kong/kubernetes-ingress-controller/issues/5395
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 vault configuration",
},
{
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
}
Loading