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

add kafkauser annotation validations #1015

Merged
merged 5 commits into from
Jul 20, 2023
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
1 change: 1 addition & 0 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/banzaicloud/istio-client-go v0.0.17
github.com/cert-manager/cert-manager v1.11.2
github.com/imdario/mergo v0.3.13
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
gotest.tools v2.2.0+incompatible
k8s.io/api v0.26.4
k8s.io/apimachinery v0.26.4
Expand Down
2 changes: 2 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand Down
16 changes: 16 additions & 0 deletions api/v1alpha1/kafkauser_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package v1alpha1

import (
"strings"
"time"

"github.com/banzaicloud/koperator/api/util"
Expand All @@ -26,6 +27,8 @@ import (
const (
// default certificate duration if kafkauser.spec.expirationSeconds is not set
defaultCertificateDuration = time.Hour * 24 * 90
// CertManagerSignerNamePrefix is acceptable pki backend signerName prefix for cert-manager
CertManagerSignerNamePrefix string = "clusterissuers.cert-manager.io"
)

// KafkaUserSpec defines the desired state of KafkaUser
Expand Down Expand Up @@ -110,6 +113,19 @@ func (spec *KafkaUserSpec) GetAnnotations() map[string]string {
return util.CloneMap(spec.Annotations)
}

// ValidateAnnotations checks if certificate signing request annotations are valid
func (spec *KafkaUserSpec) ValidateAnnotations() error {
// Validate annotations for cert-manager pki backend signer
if strings.Split(spec.PKIBackendSpec.SignerName, "/")[0] == CertManagerSignerNamePrefix {
certManagerCSRAnnotations := newCertManagerSignerAnnotationsWithValidators()
err := certManagerCSRAnnotations.validate(spec.GetAnnotations())
if err != nil {
return err
}
pregnor marked this conversation as resolved.
Show resolved Hide resolved
}
return nil
}

func (spec *KafkaUserSpec) GetExpirationSeconds() int32 {
if spec.ExpirationSeconds == nil {
return int32(defaultCertificateDuration.Seconds())
Expand Down
103 changes: 103 additions & 0 deletions api/v1alpha1/kafkauser_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright © 2023 Cisco Systems, Inc. and/or its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1alpha1

import (
"fmt"
"testing"

"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/banzaicloud/koperator/api/v1beta1"
)

const (
testNamespace = "test-namespace"
testCertManagerSignerName = CertManagerSignerNamePrefix + "/test-issuer"
)

func TestKafkaUserSpecValidateAnnotations(t *testing.T) {
t.Parallel()
type args struct {
annotations map[string]string
}
kafkaUser := &KafkaUser{
TypeMeta: metav1.TypeMeta{
Kind: "KafkaUser",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-user",
Namespace: testNamespace,
},
Spec: KafkaUserSpec{
SecretName: "test-secret",
PKIBackendSpec: &PKIBackendSpec{
PKIBackend: string(v1beta1.PKIBackendK8sCSR),
SignerName: testCertManagerSignerName,
},
},
}

tests := []struct {
name string
args args
wanted error
}{
{
name: "no invalid annotations",
args: args{
annotations: map[string]string{
"experimental.cert-manager.io/request-duration": "2880h",
},
},
wanted: nil,
},
{
name: "valid annotation, invalid value",
args: args{
annotations: map[string]string{
"experimental.cert-manager.io/request-duration": "2880",
},
},
wanted: fmt.Errorf("could not parse certificate request duration: time: missing unit in duration \"2880\""),
},
{
name: "invalid annotation",
args: args{
annotations: map[string]string{
"test-annotation": "test",
},
},
wanted: newCertManagerSignerAnnotationsWithValidators().getNotSupportedAnnotationError(),
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
k := kafkaUser.DeepCopy()
k.Spec.Annotations = tt.args.annotations
found := k.Spec.ValidateAnnotations()

if tt.wanted != nil && found != nil {
assert.Equal(t, tt.wanted.Error(), found.Error())
} else {
assert.Equal(t, tt.wanted, found)
}
})
}
}
68 changes: 68 additions & 0 deletions api/v1alpha1/kafkauserspec_annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright © 2023 Cisco Systems, Inc. and/or its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1alpha1

import (
"fmt"
"time"

"emperror.dev/errors"
"golang.org/x/exp/maps"
)

// annotationsWithValidations is a map whose keys are KafkaUserSpec annotation keys, and values are validators
type annotationsWithValidations map[string]annotationValidator

func (a annotationsWithValidations) validate(as map[string]string) error {
for key, value := range as {
validator, ok := a[key]
if !ok {
return a.getNotSupportedAnnotationError()
}
err := validator.validate(value)
if err != nil {
return err
}
}
return nil
}

func (a annotationsWithValidations) getNotSupportedAnnotationError() error {
return fmt.Errorf("kafkauser annotations contain a not supported annotation for the signer, supported annotations: %v", maps.Keys(a))
}

// annotationValidator is used to implement a single annotation validator
type annotationValidator interface {
validate(string) error
}

// newCertManagerSignerAnnotationsWithValidators returns annotationsWithValidations for cert-manager pki backend signer
func newCertManagerSignerAnnotationsWithValidators() annotationsWithValidations {
var c certManagerRequestDurationValidator
return annotationsWithValidations{
"experimental.cert-manager.io/request-duration": &c,
}
}

// certManagerRequestDurationValidator implements annotationValidator interface
type certManagerRequestDurationValidator func(string) error

func (c certManagerRequestDurationValidator) validate(a string) error {
_, err := time.ParseDuration(a)
if err != nil {
return errors.WrapIf(err, "could not parse certificate request duration")
}
return nil
}
Loading