Skip to content
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
57 changes: 16 additions & 41 deletions pkg/tlsutil/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ import (
"fmt"
"strings"

"github.com/operator-framework/operator-sdk/pkg/sdk"

"k8s.io/api/core/v1"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
)

// CertType defines the type of the cert.
Expand Down Expand Up @@ -130,7 +129,13 @@ const (
TLSCACertKey = "ca.crt"
)

// NewSDKCertGenerator constructs a new CertGenerator given the kubeClient.
func NewSDKCertGenerator(kubeClient kubernetes.Interface) CertGenerator {
return &SDKCertGenerator{KubeClient: kubeClient}
}

type SDKCertGenerator struct {
KubeClient kubernetes.Interface
}

type keyAndCert struct {
Expand All @@ -147,11 +152,11 @@ func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service
if err != nil {
return nil, nil, nil, err
}
appSecret, err := getAppSecretInCluster(toAppSecretName(k, n, config.CertName), ns)
appSecret, err := getAppSecretInCluster(scg.KubeClient, ToAppSecretName(k, n, config.CertName), ns)
if err != nil {
return nil, nil, nil, err
}
caSecret, caConfigMap, err := getCASecretAndConfigMapInCluster(toCASecretAndConfigMapName(k, n), ns)
caSecret, caConfigMap, err := getCASecretAndConfigMapInCluster(scg.KubeClient, ToCASecretAndConfigMapName(k, n), ns)
if err != nil {
return nil, nil, nil, err
}
Expand Down Expand Up @@ -181,26 +186,16 @@ func verifyConfig(config *CertConfig) error {
return nil
}

func toAppSecretName(kind, name, certName string) string {
func ToAppSecretName(kind, name, certName string) string {
return strings.ToLower(kind) + "-" + name + "-" + certName
}

func toCASecretAndConfigMapName(kind, name string) string {
func ToCASecretAndConfigMapName(kind, name string) string {
return strings.ToLower(kind) + "-" + name + "-ca"
}

func getAppSecretInCluster(name, namespace string) (*v1.Secret, error) {
se := &v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
err := sdk.Get(se)
func getAppSecretInCluster(kubeClient kubernetes.Interface, name, namespace string) (*v1.Secret, error) {
se, err := kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
if apiErrors.IsNotFound(err) {
return nil, nil
}
Expand All @@ -212,38 +207,18 @@ func getAppSecretInCluster(name, namespace string) (*v1.Secret, error) {

// getCASecretAndConfigMapInCluster gets CA secret and configmap of the given name and namespace.
// it only returns both if they are found and nil if both are not found. In the case if only one of them is found, then we error out because we expect either both CA secret and configmap exit or not.
func getCASecretAndConfigMapInCluster(name, namespace string) (*v1.Secret, *v1.ConfigMap, error) {
cm := &v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
func getCASecretAndConfigMapInCluster(kubeClient kubernetes.Interface, name, namespace string) (*v1.Secret, *v1.ConfigMap, error) {
hasConfigMap := true
err := sdk.Get(cm)
cm, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{})
if apiErrors.IsNotFound(err) {
hasConfigMap = false
}
if err != nil {
return nil, nil, err
}

se := &v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
hasSecret := true
err = sdk.Get(se)
se, err := kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
if apiErrors.IsNotFound(err) {
hasSecret = false
}
Expand Down
103 changes: 103 additions & 0 deletions test/e2e/tls_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2018 The Operator-SDK Authors
//
// 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 e2e

import (
"reflect"
"testing"

"github.com/operator-framework/operator-sdk/pkg/tlsutil"
framework "github.com/operator-framework/operator-sdk/test/e2e/framework"

"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// TestBothAppAndCATLSAssetsExist ensures that when both application
// and CA TLS assets exist in the k8s cluster for a given cr,
// the GenerateCert() simply returns those to the caller.
func TestBothAppAndCATLSAssetsExist(t *testing.T) {
f := framework.Global
ctx := f.NewTestCtx(t)
defer ctx.Cleanup(t)
namespace, err := ctx.GetNamespace()
if err != nil {
t.Fatal(err)
}

// Use Pod as a dummy runtime object for the CR input of GenerateCert().
crKind := "Pod"
crName := "example-pod"
mCR := &v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: crKind,
},
ObjectMeta: metav1.ObjectMeta{
Name: crName,
Namespace: namespace,
},
}

certName := "app-cert"
appSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: tlsutil.ToAppSecretName(crKind, crName, certName),
},
}
appSecret, err = f.KubeClient.CoreV1().Secrets(namespace).Create(appSecret)
if err != nil {
t.Fatal(err)
}

caConfigMapAndSecretName := tlsutil.ToCASecretAndConfigMapName(crKind, crName)
caConfigMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: caConfigMapAndSecretName,
},
}
caConfigMap, err = f.KubeClient.CoreV1().ConfigMaps(namespace).Create(caConfigMap)
if err != nil {
t.Fatal(err)
}

caSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: caConfigMapAndSecretName,
},
}
caSecret, err = f.KubeClient.CoreV1().Secrets(namespace).Create(caSecret)
if err != nil {
t.Fatal(err)
}

cg := tlsutil.NewSDKCertGenerator(f.KubeClient)
ccfg := &tlsutil.CertConfig{
CertName: certName,
}
actualAppSecret, actualCaConfigMap, actualCaSecret, err := cg.GenerateCert(mCR, nil, ccfg)
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(appSecret, actualAppSecret) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid doing DeepEqual() for the entire objects. That's usually not very helpful as things like resourceVersion might change if the object is updated after creation. Or some label or annotation might be added by the server after creation.

The checks should be as specific as possible. Either check something in the spec to verify equality. Or do it for the UID which uniquely identifies the object to verify they're both the same objects.

if !reflect.DeepEqual(appSecret.UID, actualAppSecret.UID)

or

if appSecret.UID != actualAppSecret.UID

since they're just strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's usually not very helpful as things like resourceVersion might change if the object is updated after creation
This shouldn't happen because we don't update the secrets and configmap in this test case. I expect the created objects are the exact same as the one returned by GenerateCert().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay for this case but my general point is when either reconciling or checking a test condition we should be as specific as possible when checking the equality of two objects.

In this case even if say the resourceVersion changes (for whatever reason) our test should not fail because we just care if it's the same secret object that we created earlier. And that's verifiable by just looking at something unique(like the UID).
Although even the name/namespace uniquely identifies the secret so we don't even need this check strictly speaking.

But you can keep it as !reflect.DeepEqual(appSecret.UID, actualAppSecret.UID) for this case.

t.Fatalf("expect %+v, got %+v", appSecret, actualAppSecret)
}
if !reflect.DeepEqual(caConfigMap, actualCaConfigMap) {
t.Fatalf("expect %+v, got %+v", caConfigMap, actualCaConfigMap)
}
if !reflect.DeepEqual(caSecret, actualCaSecret) {
t.Fatalf("expect %+v, got %+v", caSecret, actualCaSecret)
}
}