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

feat: Introduce Dedicated Istio Gateway Secret #2004

Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
47f48f4
feat: Introduce Dedicated Istio Gateway Secret
LeelaChacha Nov 3, 2024
2b89a3a
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 3, 2024
a8217b5
refactor: Lint
LeelaChacha Nov 4, 2024
c76cffa
refactor: Lint
LeelaChacha Nov 4, 2024
d4935ad
test: Simulate Gateway Secret in Watcher Suite
LeelaChacha Nov 4, 2024
b71c975
refactor: gofumpt and gci
LeelaChacha Nov 4, 2024
897dd0f
fix: ca rotation e2e test
LeelaChacha Nov 4, 2024
5cb068d
test: ca rotation e2e fix
LeelaChacha Nov 6, 2024
0596448
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 6, 2024
d48fd6f
test: ca rotation e2e fix
LeelaChacha Nov 7, 2024
6e35338
fix: give controller-manager permission to update istio-gateway-secret
LeelaChacha Nov 7, 2024
e4edfb6
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 7, 2024
aeacc42
fix: give controller-manager permission to update istio-gateway-secret
LeelaChacha Nov 7, 2024
758c4d3
fix: KLM image name in kustomization
LeelaChacha Nov 7, 2024
8cb3b38
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 11, 2024
ed7ea63
refactor: Rename zerodw package
LeelaChacha Nov 11, 2024
7016aac
refactor: PR Review
LeelaChacha Nov 14, 2024
bf0d76b
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 14, 2024
b63ffe4
refactor: PR Review
LeelaChacha Nov 14, 2024
b467d13
refactor: gci
LeelaChacha Nov 14, 2024
333fd09
refactor: hide gateway secret name
LeelaChacha Nov 14, 2024
f7fc41c
test: add e2e test for istio gateway secret rotation
LeelaChacha Nov 15, 2024
ce69e8c
refactor: gci and gofumpt
LeelaChacha Nov 15, 2024
16848db
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 15, 2024
5e71352
fix: flags_test.go
LeelaChacha Nov 15, 2024
11779fe
chore: remove debug print statement
LeelaChacha Nov 15, 2024
34363c6
fix: remove background context
LeelaChacha Nov 15, 2024
55fe04b
fix: test logics
LeelaChacha Nov 18, 2024
0ceb574
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 18, 2024
45691cb
refactor: go routine style in main
LeelaChacha Nov 18, 2024
b260cbf
fix: KLM health check
LeelaChacha Nov 18, 2024
7f1110c
fix: Istion Rotation E2E Check
LeelaChacha Nov 18, 2024
0453158
fix: Istion Rotation E2E Check
LeelaChacha Nov 19, 2024
645dbdb
test: Add Unit Tests for handler.go
LeelaChacha Nov 20, 2024
25fb3a2
fix: Event name in unit test
LeelaChacha Nov 20, 2024
2fa53c8
refactor: require.Equal parameter order
LeelaChacha Nov 20, 2024
c8d2b0e
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 20, 2024
9f6646d
refactor: shift namespace const to testutils package
LeelaChacha Nov 20, 2024
414f368
refactor: using require.Equal
LeelaChacha Nov 20, 2024
841b97d
refactor: PR comments
LeelaChacha Nov 21, 2024
cc784ea
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 21, 2024
b03cd8f
refactor: handler unit test, remove secret manager interface
LeelaChacha Nov 25, 2024
43b4bcd
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 25, 2024
4f899dc
refactor: lint gci and gofumpt
LeelaChacha Nov 25, 2024
8370566
refactor: unparam lint error
LeelaChacha Nov 25, 2024
6eccbd8
refactor: unparam lint error
LeelaChacha Nov 25, 2024
8490271
refactor: unparam lint error
LeelaChacha Nov 25, 2024
5aaaab9
Merge branch 'main' into feature/#1890-tls-cert-rotation-migration
LeelaChacha Nov 26, 2024
ca5e190
chore: add pkg/gatewaysecret unittest coverage
LeelaChacha Nov 27, 2024
203d529
chore: add pkg/gatewaysecret unittest coverage
LeelaChacha Nov 27, 2024
f098fc6
refactor: make ManageGatewaySecret private
LeelaChacha Nov 27, 2024
2207900
refactor: remove local testing artefacts
LeelaChacha Nov 27, 2024
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
3 changes: 2 additions & 1 deletion .github/actions/deploy-lifecycle-manager-e2e/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ runs:
kustomize edit add patch --path requeue-interval-patch.yaml --kind Deployment
popd
- name: Patch CA certificate renewBefore
if: ${{matrix.e2e-test == 'ca-certificate-rotation'}}
if: ${{matrix.e2e-test == 'ca-certificate-rotation' ||
matrix.e2e-test == 'istio-gateway-secret-rotation'}}
working-directory: lifecycle-manager
shell: bash
run: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test-e2e-with-modulereleasemeta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
- unmanage-module
- skip-manifest-reconciliation
- ca-certificate-rotation
- istio-gateway-secret-rotation
- self-signed-certificate-rotation
- mandatory-module
- mandatory-module-metrics
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
- module-install-by-version
- skip-manifest-reconciliation
- ca-certificate-rotation
- istio-gateway-secret-rotation
- self-signed-certificate-rotation
- mandatory-module
- mandatory-module-metrics
Expand Down
14 changes: 12 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
machineryruntime "k8s.io/apimachinery/pkg/runtime"
machineryutilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes"
k8sclientscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
Expand All @@ -61,6 +63,7 @@ import (
"github.com/kyma-project/lifecycle-manager/internal/pkg/flags"
"github.com/kyma-project/lifecycle-manager/internal/pkg/metrics"
"github.com/kyma-project/lifecycle-manager/internal/remote"
"github.com/kyma-project/lifecycle-manager/pkg/gatewaysecret"
"github.com/kyma-project/lifecycle-manager/pkg/log"
"github.com/kyma-project/lifecycle-manager/pkg/matcher"
"github.com/kyma-project/lifecycle-manager/pkg/queue"
Expand Down Expand Up @@ -201,12 +204,21 @@ func setupManager(flagVar *flags.FlagVar, cacheOptions cache.Options, scheme *ma
go cleanupStoredVersions(flagVar.DropCrdStoredVersionMap, mgr, setupLog)
go scheduleMetricsCleanup(kymaMetrics, flagVar.MetricsCleanupIntervalInMinutes, mgr, setupLog)

go setupIstioGatewaySecretRotation(config, kcpClient, setupLog)

if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(runtimeProblemExitCode)
}
}

func setupIstioGatewaySecretRotation(config *rest.Config, kcpClient *remote.ConfigAndClient, setupLog logr.Logger) {
kcpClientset := kubernetes.NewForConfigOrDie(config)
gatewaySecretHandler := gatewaysecret.NewGatewaySecretHandler(kcpClient)

gatewaysecret.StartRootCertificateWatch(kcpClientset, gatewaySecretHandler, setupLog)
}

func addHealthChecks(mgr manager.Manager, setupLog logr.Logger) {
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
Expand Down Expand Up @@ -319,7 +331,6 @@ func setupKymaReconciler(mgr ctrl.Manager, descriptorProvider *provider.CachedDe
func createSkrWebhookManager(mgr ctrl.Manager, skrContextFactory remote.SkrContextProvider,
flagVar *flags.FlagVar,
) (*watcher.SKRWebhookManifestManager, error) {
caCertificateCache := watcher.NewCACertificateCache(flagVar.CaCertCacheTTL)
config := watcher.SkrWebhookManagerConfig{
SKRWatcherPath: flagVar.WatcherResourcesPath,
SkrWatcherImage: flagVar.GetWatcherImage(),
Expand Down Expand Up @@ -349,7 +360,6 @@ func createSkrWebhookManager(mgr ctrl.Manager, skrContextFactory remote.SkrConte
return watcher.NewSKRWebhookManifestManager(
mgr.GetClient(),
skrContextFactory,
caCertificateCache,
config,
certConfig,
resolvedKcpAddr)
Expand Down
4 changes: 2 additions & 2 deletions config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ generatorOptions:

images:
- name: controller
newName: europe-docker.pkg.dev/kyma-project/prod/lifecycle-manager
newTag: latest
newName: k3d-kcp-registry.localhost:5000/lifecycle-manager
Tomasz-Smelcerz-SAP marked this conversation as resolved.
Show resolved Hide resolved
newTag: "20241114183639"
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ rules:
- watch
- create
- delete
- update
- apiGroups:
- cert-manager.io
resources:
Expand Down
2 changes: 1 addition & 1 deletion config/watcher/gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ spec:
number: 443
protocol: HTTPS
tls:
credentialName: klm-watcher
credentialName: klm-istio-gateway
mode: MUTUAL
4 changes: 0 additions & 4 deletions internal/pkg/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const (
DefaultIstioGatewayNamespace = "kcp-system"
DefaultIstioNamespace = "istio-system"
DefaultCaCertName = "klm-watcher-serving"
DefaultCaCertCacheTTL time.Duration = 1 * time.Hour
DefaultSelfSignedCertDuration time.Duration = 90 * 24 * time.Hour
DefaultSelfSignedCertRenewBefore time.Duration = 60 * 24 * time.Hour
DefaultSelfSignedCertificateRenewBuffer = 24 * time.Hour
Expand Down Expand Up @@ -204,8 +203,6 @@ func DefineFlagVar() *FlagVar {
"Name of the namespace for syncing remote Kyma and module catalog")
flag.StringVar(&flagVar.CaCertName, "ca-cert-name", DefaultCaCertName,
"Name of the CA Certificate in Istio Namespace which is used to sign SKR Certificates")
flag.DurationVar(&flagVar.CaCertCacheTTL, "ca-cert-cache-ttl", DefaultCaCertCacheTTL,
"The ttl for the CA Certificate Cache")
flag.DurationVar(&flagVar.SelfSignedCertDuration, "self-signed-cert-duration", DefaultSelfSignedCertDuration,
"The lifetime duration of self-signed certificate, minimum accepted duration is 1 hour.")
flag.DurationVar(&flagVar.SelfSignedCertRenewBefore, "self-signed-cert-renew-before",
Expand Down Expand Up @@ -288,7 +285,6 @@ type FlagVar struct {
SkipPurgingFor string
RemoteSyncNamespace string
CaCertName string
CaCertCacheTTL time.Duration
IsKymaManaged bool
SelfSignedCertDuration time.Duration
SelfSignedCertRenewBefore time.Duration
Expand Down
5 changes: 0 additions & 5 deletions internal/pkg/flags/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,6 @@ func Test_ConstantFlags(t *testing.T) {
constValue: DefaultCaCertName,
expectedValue: "klm-watcher-serving",
},
{
constName: "DefaultCaCertCacheTTL",
constValue: DefaultCaCertCacheTTL.String(),
expectedValue: (1 * time.Hour).String(),
},
{
constName: "DefaultSelfSignedCertDuration",
constValue: DefaultSelfSignedCertDuration.String(),
Expand Down
203 changes: 203 additions & 0 deletions pkg/gatewaysecret/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package gatewaysecret
Tomasz-Smelcerz-SAP marked this conversation as resolved.
Show resolved Hide resolved

import (
"context"
"errors"
"fmt"
"time"

certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
"github.com/go-logr/logr"
apicorev1 "k8s.io/api/core/v1"
apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/kyma-project/lifecycle-manager/pkg/util"
)

const (
LastModifiedAtAnnotation = "lastModifiedAt"
gatewaySecretName = "klm-istio-gateway" //nolint:gosec // gatewaySecretName is not a credential
kcpRootSecretName = "klm-watcher"
kcpCACertName = "klm-watcher-serving"
istioNamespace = "istio-system"
)

var errCouldNotGetLastModifiedAt = errors.New("getting lastModifiedAt time failed")

type GatewaySecretHandler struct {
kcpClient client.Client
}

func NewGatewaySecretHandler(kcpClient client.Client) *GatewaySecretHandler {
return &GatewaySecretHandler{
kcpClient: kcpClient,
}
}

func (gsh *GatewaySecretHandler) ManageGatewaySecret(ctx context.Context, rootSecret *apicorev1.Secret) error {
gwSecret, err := gsh.FindGatewaySecret(ctx)

if util.IsNotFound(err) {
return gsh.handleNonExisting(ctx, rootSecret)
}
if err != nil {
return err
}

return gsh.handleExisting(ctx, rootSecret, gwSecret)
}

func (gsh *GatewaySecretHandler) handleNonExisting(ctx context.Context, rootSecret *apicorev1.Secret) error {
gwSecret := NewGatewaySecret(rootSecret)
return gsh.Create(ctx, gwSecret)
}

func (gsh *GatewaySecretHandler) handleExisting(ctx context.Context,
rootSecret *apicorev1.Secret, gwSecret *apicorev1.Secret,
) error {
caCert, err := gsh.GetRootCACertificate(ctx)
if err != nil {
return err
}
if !GatewaySecretRequiresUpdate(gwSecret, caCert) {
return nil
}
CopyRootSecretDataIntoGatewaySecret(gwSecret, rootSecret)
return gsh.Update(ctx, gwSecret)
}

func CopyRootSecretDataIntoGatewaySecret(gwSecret *apicorev1.Secret, rootSecret *apicorev1.Secret) {
gwSecret.Data["tls.crt"] = rootSecret.Data["tls.crt"]
gwSecret.Data["tls.key"] = rootSecret.Data["tls.key"]
gwSecret.Data["ca.crt"] = rootSecret.Data["ca.crt"]
}

func GatewaySecretRequiresUpdate(gwSecret *apicorev1.Secret, caCert certmanagerv1.Certificate) bool {
if gwSecretLastModifiedAt, err := GetValidLastModifiedAt(gwSecret); err == nil {
if caCert.Status.NotBefore != nil && gwSecretLastModifiedAt.After(caCert.Status.NotBefore.Time) {
return false
}
}
return true
}

func GetValidLastModifiedAt(secret *apicorev1.Secret) (time.Time, error) {
if gwSecretLastModifiedAtValue, ok := secret.Annotations[LastModifiedAtAnnotation]; ok {
if gwSecretLastModifiedAt, err := time.Parse(time.RFC3339, gwSecretLastModifiedAtValue); err == nil {
return gwSecretLastModifiedAt, nil
}
}
return time.Time{}, errCouldNotGetLastModifiedAt
}

func (gsh *GatewaySecretHandler) FindGatewaySecret(ctx context.Context) (*apicorev1.Secret, error) {
return GetGatewaySecret(ctx, gsh.kcpClient)
}

func (gsh *GatewaySecretHandler) Create(ctx context.Context, secret *apicorev1.Secret) error {
gsh.updateLastModifiedAt(secret)
if err := gsh.kcpClient.Create(ctx, secret); err != nil {
return fmt.Errorf("failed to create secret %s: %w", secret.Name, err)
}
return nil
}

func (gsh *GatewaySecretHandler) Update(ctx context.Context, secret *apicorev1.Secret) error {
gsh.updateLastModifiedAt(secret)
if err := gsh.kcpClient.Update(ctx, secret); err != nil {
return fmt.Errorf("failed to update secret %s: %w", secret.Name, err)
}
return nil
}

func (gsh *GatewaySecretHandler) GetRootCACertificate(ctx context.Context) (certmanagerv1.Certificate, error) {
caCert := certmanagerv1.Certificate{}
if err := gsh.kcpClient.Get(ctx,
client.ObjectKey{Namespace: istioNamespace, Name: kcpCACertName},
&caCert); err != nil {
return certmanagerv1.Certificate{}, fmt.Errorf("failed to get CA certificate: %w", err)
}
return caCert, nil
}

func (gsh *GatewaySecretHandler) updateLastModifiedAt(secret *apicorev1.Secret) {
if secret.Annotations == nil {
secret.Annotations = make(map[string]string)
}
secret.Annotations[LastModifiedAtAnnotation] = apimetav1.Now().Format(time.RFC3339)
}

func NewGatewaySecret(rootSecret *apicorev1.Secret) *apicorev1.Secret {
gwSecret := &apicorev1.Secret{
TypeMeta: apimetav1.TypeMeta{
Kind: "Secret",
APIVersion: apicorev1.SchemeGroupVersion.String(),
},
ObjectMeta: apimetav1.ObjectMeta{
Name: gatewaySecretName,
Namespace: istioNamespace,
},
Data: map[string][]byte{
"tls.crt": rootSecret.Data["tls.crt"],
"tls.key": rootSecret.Data["tls.key"],
"ca.crt": rootSecret.Data["ca.crt"],
},
}
return gwSecret
}

func GetGatewaySecret(ctx context.Context, clnt client.Client) (*apicorev1.Secret, error) {
secret := &apicorev1.Secret{}
if err := clnt.Get(ctx, client.ObjectKey{
Name: gatewaySecretName,
Namespace: istioNamespace,
}, secret); err != nil {
return nil, fmt.Errorf("failed to get secret %s: %w", gatewaySecretName, err)
}
return secret, nil
}

func StartRootCertificateWatch(clientset *kubernetes.Clientset, gatewaySecretHandler *GatewaySecretHandler,
log logr.Logger,
) {
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()

secretWatch, err := clientset.CoreV1().Secrets(istioNamespace).Watch(ctx, apimetav1.ListOptions{
LeelaChacha marked this conversation as resolved.
Show resolved Hide resolved
FieldSelector: fields.OneTermEqualSelector(apimetav1.ObjectNameField, kcpRootSecretName).String(),
})
if err != nil {
log.Error(err, "unable to start watching root certificate")
panic(err)
}

WatchEvents(ctx, secretWatch.ResultChan(), gatewaySecretHandler.ManageGatewaySecret, log)
}

func WatchEvents(ctx context.Context, watchEvents <-chan watch.Event,
manageGatewaySecretFunc func(context.Context, *apicorev1.Secret) error, log logr.Logger,
) {
for event := range watchEvents {
rootCASecret, _ := event.Object.(*apicorev1.Secret)

switch event.Type {
case watch.Added, watch.Modified:
err := manageGatewaySecretFunc(ctx, rootCASecret)
if err != nil {
log.Error(err, "unable to manage istio gateway secret")
}
case watch.Deleted:
LeelaChacha marked this conversation as resolved.
Show resolved Hide resolved
// ignored because it is an invalid state and cert manager should not delete the root secret
// even if it is deleted, the certificate manager will recreate it, and trigger the watch event
fallthrough
case watch.Error, watch.Bookmark:
fallthrough
default:
continue
}
}
}
Loading
Loading