Skip to content

Commit

Permalink
Add tests for custom number of fernet keys
Browse files Browse the repository at this point in the history
  • Loading branch information
Andre Aranha authored and afaranha committed Nov 5, 2024
1 parent ca16682 commit 05e2403
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 12 deletions.
8 changes: 2 additions & 6 deletions controllers/keystoneapi_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1189,7 +1189,8 @@ func (r *KeystoneAPIReconciler) generateServiceConfigMaps(
instance.Status.DatabaseHostname,
keystone.DatabaseName,
),
"enableSecureRBAC": instance.Spec.EnableSecureRBAC,
"enableSecureRBAC": instance.Spec.EnableSecureRBAC,
"fernetMaxActiveKeys": instance.Spec.FernetMaxActiveKeys,
}

// create httpd vhost template parameters
Expand Down Expand Up @@ -1454,11 +1455,6 @@ func (r *KeystoneAPIReconciler) ensureFernetKeys(
return nil
}

fernetKeys := make(map[string]string, len(secret.Data))
for k, v := range secret.Data {
fernetKeys[k] = string(v[:])
}

secret.Annotations[fernetAnnotation] = now.Format(time.RFC3339)

// use update to apply changes to the secret, since EnsureSecrets
Expand Down
2 changes: 1 addition & 1 deletion templates/keystoneapi/config/keystone.conf
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ enforce_scope = {{ .enableSecureRBAC }}

[fernet_tokens]
key_repository=/etc/keystone/fernet-keys
max_active_keys=2
max_active_keys={{ .fernetMaxActiveKeys }}

{{ if (index . "TransportURL") }}
[oslo_messaging_notifications]
Expand Down
15 changes: 10 additions & 5 deletions tests/functional/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,20 @@ import (
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
)

func GetDefaultKeystoneAPISpec() map[string]interface{} {
func GetKeystoneAPISpec(fernetMaxKeys int32) map[string]interface{} {
return map[string]interface{}{
"databaseInstance": "openstack",
"replicas": 1,
"secret": SecretName,
"databaseAccount": AccountName,
"databaseInstance": "openstack",
"replicas": 1,
"secret": SecretName,
"databaseAccount": AccountName,
"fernetMaxActiveKeys": fernetMaxKeys,
}
}

func GetDefaultKeystoneAPISpec() map[string]interface{} {
return GetKeystoneAPISpec(5)
}

func GetTLSKeystoneAPISpec() map[string]interface{} {
return map[string]interface{}{
"databaseInstance": "openstack",
Expand Down
243 changes: 243 additions & 0 deletions tests/functional/keystoneapi_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
"time"

. "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports
Expand All @@ -36,6 +37,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

var _ = Describe("Keystone controller", func() {
Expand Down Expand Up @@ -1109,6 +1111,247 @@ var _ = Describe("Keystone controller", func() {
})
})

When("When FernetMaxActiveKeys is created with a number lower than 3", func() {
It("should fail", func() {
InterceptGomegaFailure(
func() {
CreateKeystoneAPI(keystoneAPIName, GetKeystoneAPISpec(-1))
})
})
})

When("When the fernet keys are created with FernetMaxActiveKeys as 3", func() {
BeforeEach(func() {
DeferCleanup(
k8sClient.Delete, ctx, CreateKeystoneMessageBusSecret(namespace, "rabbitmq-secret"))
DeferCleanup(th.DeleteInstance, CreateKeystoneAPI(keystoneAPIName, GetKeystoneAPISpec(3)))
DeferCleanup(
k8sClient.Delete, ctx, CreateKeystoneAPISecret(namespace, SecretName))
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec))
DeferCleanup(
mariadb.DeleteDBService,
mariadb.CreateDBService(
namespace,
GetKeystoneAPI(keystoneAPIName).Spec.DatabaseInstance,
corev1.ServiceSpec{
Ports: []corev1.ServicePort{{Port: 3306}},
},
),
)
mariadb.SimulateMariaDBAccountCompleted(keystoneAccountName)
mariadb.SimulateMariaDBDatabaseCompleted(keystoneDatabaseName)
infra.SimulateTransportURLReady(types.NamespacedName{
Name: fmt.Sprintf("%s-keystone-transport", keystoneAPIName.Name),
Namespace: namespace,
})
infra.SimulateMemcachedReady(types.NamespacedName{
Name: "memcached",
Namespace: namespace,
})
th.SimulateJobSuccess(dbSyncJobName)
th.SimulateJobSuccess(bootstrapJobName)
th.SimulateDeploymentReplicaReady(deploymentName)
})

It("creates 3 keys", func() {
secret := th.GetSecret(types.NamespacedName{Namespace: keystoneAPIName.Namespace, Name: "keystone"})
Expect(secret).ToNot(BeNil())

Eventually(func(g Gomega) {
numberFernetKeys := 0
for k, _ := range secret.Data {
if strings.HasPrefix(k, "FernetKeys") {
numberFernetKeys++
}
}

Expect(numberFernetKeys).Should(BeNumerically("==", 3))
for i := 0; i < 3; i++ {
g.Expect(secret.Data["FernetKeys"+strconv.Itoa(i)]).NotTo(BeNil())
}
}, timeout, interval).Should(Succeed())
})
})

When("When the fernet keys are created with FernetMaxActiveKeys as 100", func() {
BeforeEach(func() {
DeferCleanup(
k8sClient.Delete, ctx, CreateKeystoneMessageBusSecret(namespace, "rabbitmq-secret"))
DeferCleanup(th.DeleteInstance, CreateKeystoneAPI(keystoneAPIName, GetKeystoneAPISpec(100)))
DeferCleanup(
k8sClient.Delete, ctx, CreateKeystoneAPISecret(namespace, SecretName))
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec))
DeferCleanup(
mariadb.DeleteDBService,
mariadb.CreateDBService(
namespace,
GetKeystoneAPI(keystoneAPIName).Spec.DatabaseInstance,
corev1.ServiceSpec{
Ports: []corev1.ServicePort{{Port: 3306}},
},
),
)
mariadb.SimulateMariaDBAccountCompleted(keystoneAccountName)
mariadb.SimulateMariaDBDatabaseCompleted(keystoneDatabaseName)
infra.SimulateTransportURLReady(types.NamespacedName{
Name: fmt.Sprintf("%s-keystone-transport", keystoneAPIName.Name),
Namespace: namespace,
})
infra.SimulateMemcachedReady(types.NamespacedName{
Name: "memcached",
Namespace: namespace,
})
th.SimulateJobSuccess(dbSyncJobName)
th.SimulateJobSuccess(bootstrapJobName)
th.SimulateDeploymentReplicaReady(deploymentName)
})

It("creates 100 keys", func() {
secret := th.GetSecret(types.NamespacedName{Namespace: keystoneAPIName.Namespace, Name: "keystone"})
Expect(secret).ToNot(BeNil())

Eventually(func(g Gomega) {
numberFernetKeys := 0
for k, _ := range secret.Data {
if strings.HasPrefix(k, "FernetKeys") {
numberFernetKeys++
}
}

Expect(numberFernetKeys).Should(BeNumerically("==", 100))
for i := 0; i < 100; i++ {
g.Expect(secret.Data["FernetKeys"+strconv.Itoa(i)]).NotTo(BeNil())
}
}, timeout, interval).Should(Succeed())
})
})

When("When the fernet keys are updated from 5 to 4", func() {
BeforeEach(func() {
DeferCleanup(
k8sClient.Delete, ctx, CreateKeystoneMessageBusSecret(namespace, "rabbitmq-secret"))
DeferCleanup(th.DeleteInstance, CreateKeystoneAPI(keystoneAPIName, GetDefaultKeystoneAPISpec()))
DeferCleanup(
k8sClient.Delete, ctx, CreateKeystoneAPISecret(namespace, SecretName))
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec))
DeferCleanup(
mariadb.DeleteDBService,
mariadb.CreateDBService(
namespace,
GetKeystoneAPI(keystoneAPIName).Spec.DatabaseInstance,
corev1.ServiceSpec{
Ports: []corev1.ServicePort{{Port: 3306}},
},
),
)
mariadb.SimulateMariaDBAccountCompleted(keystoneAccountName)
mariadb.SimulateMariaDBDatabaseCompleted(keystoneDatabaseName)
infra.SimulateTransportURLReady(types.NamespacedName{
Name: fmt.Sprintf("%s-keystone-transport", keystoneAPIName.Name),
Namespace: namespace,
})
infra.SimulateMemcachedReady(types.NamespacedName{
Name: "memcached",
Namespace: namespace,
})
th.SimulateJobSuccess(dbSyncJobName)
th.SimulateJobSuccess(bootstrapJobName)
th.SimulateDeploymentReplicaReady(deploymentName)
})

It("removes the additional key", func() {
secret := th.GetSecret(types.NamespacedName{Namespace: keystoneAPIName.Namespace, Name: "keystone"})
Expect(secret).ToNot(BeNil())

keystone := GetKeystoneAPI(keystoneAPIName)

_, err := controllerutil.CreateOrPatch(
th.Ctx, th.K8sClient, keystone, func() error {
keystone.Spec.FernetMaxActiveKeys = ptr.To(int32(4))
return nil
})
Expect(err).ToNot(HaveOccurred())

Eventually(func(g Gomega) {
secret = th.GetSecret(types.NamespacedName{Namespace: keystoneAPIName.Namespace, Name: "keystone"})
numberFernetKeys := 0
for k, _ := range secret.Data {
if strings.HasPrefix(k, "FernetKeys") {
numberFernetKeys++
}
}

g.Expect(numberFernetKeys).Should(BeNumerically("==", 4))
for i := 0; i < 4; i++ {
g.Expect(secret.Data["FernetKeys"+strconv.Itoa(i)]).NotTo(BeNil())
}
}, timeout, interval).Should(Succeed())
})
})

When("When the fernet keys are updated from 5 to 6", func() {
BeforeEach(func() {
DeferCleanup(
k8sClient.Delete, ctx, CreateKeystoneMessageBusSecret(namespace, "rabbitmq-secret"))
DeferCleanup(th.DeleteInstance, CreateKeystoneAPI(keystoneAPIName, GetDefaultKeystoneAPISpec()))
DeferCleanup(
k8sClient.Delete, ctx, CreateKeystoneAPISecret(namespace, SecretName))
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec))
DeferCleanup(
mariadb.DeleteDBService,
mariadb.CreateDBService(
namespace,
GetKeystoneAPI(keystoneAPIName).Spec.DatabaseInstance,
corev1.ServiceSpec{
Ports: []corev1.ServicePort{{Port: 3306}},
},
),
)
mariadb.SimulateMariaDBAccountCompleted(keystoneAccountName)
mariadb.SimulateMariaDBDatabaseCompleted(keystoneDatabaseName)
infra.SimulateTransportURLReady(types.NamespacedName{
Name: fmt.Sprintf("%s-keystone-transport", keystoneAPIName.Name),
Namespace: namespace,
})
infra.SimulateMemcachedReady(types.NamespacedName{
Name: "memcached",
Namespace: namespace,
})
th.SimulateJobSuccess(dbSyncJobName)
th.SimulateJobSuccess(bootstrapJobName)
th.SimulateDeploymentReplicaReady(deploymentName)
})

It("creates an additional key", func() {
secret := th.GetSecret(types.NamespacedName{Namespace: keystoneAPIName.Namespace, Name: "keystone"})
Expect(secret).ToNot(BeNil())

keystone := GetKeystoneAPI(keystoneAPIName)

_, err := controllerutil.CreateOrPatch(
th.Ctx, th.K8sClient, keystone, func() error {
keystone.Spec.FernetMaxActiveKeys = ptr.To(int32(6))
return nil
})
Expect(err).ToNot(HaveOccurred())

Eventually(func(g Gomega) {
secret = th.GetSecret(types.NamespacedName{Namespace: keystoneAPIName.Namespace, Name: "keystone"})
numberFernetKeys := 0
for k, _ := range secret.Data {
if strings.HasPrefix(k, "FernetKeys") {
numberFernetKeys++
}
}

g.Expect(numberFernetKeys).Should(BeNumerically("==", 6))
for i := 0; i < 6; i++ {
g.Expect(secret.Data["FernetKeys"+strconv.Itoa(i)]).NotTo(BeNil())
}
}, timeout, interval).Should(Succeed())
})
})

// Set rotated at to past date, triggering rotation
When("When the fernet token rotate", func() {
BeforeEach(func() {
Expand Down

0 comments on commit 05e2403

Please sign in to comment.