diff --git a/pkg/deployment/reconcile/action_tls_ca_append.go b/pkg/deployment/reconcile/action_tls_ca_append.go index 06a307aac..7feb6f8a9 100644 --- a/pkg/deployment/reconcile/action_tls_ca_append.go +++ b/pkg/deployment/reconcile/action_tls_ca_append.go @@ -78,7 +78,7 @@ func (a *appendTLSCACertificateAction) Start(ctx context.Context) (bool, error) return true, nil } - ca, _, err := getKeyCertFromSecret(a.log, caSecret, resources.CACertName, resources.CAKeyName) + ca, _, err := resources.GetKeyCertFromSecret(a.log, caSecret, resources.CACertName, resources.CAKeyName) if err != nil { a.log.Warn().Err(err).Msgf("Cert %s is invalid", resources.GetCASecretName(a.actionCtx.GetAPIObject())) return true, nil diff --git a/pkg/deployment/reconcile/action_tls_ca_clean.go b/pkg/deployment/reconcile/action_tls_ca_clean.go index bc172aead..ad44c2cc9 100644 --- a/pkg/deployment/reconcile/action_tls_ca_clean.go +++ b/pkg/deployment/reconcile/action_tls_ca_clean.go @@ -79,7 +79,7 @@ func (a *cleanTLSCACertificateAction) Start(ctx context.Context) (bool, error) { return true, nil } - ca, _, err := getKeyCertFromSecret(a.log, caSecret, resources.CACertName, resources.CAKeyName) + ca, _, err := resources.GetKeyCertFromSecret(a.log, caSecret, resources.CACertName, resources.CAKeyName) if err != nil { a.log.Warn().Err(err).Msgf("Cert %s is invalid", resources.GetCASecretName(a.actionCtx.GetAPIObject())) return true, nil diff --git a/pkg/deployment/reconcile/action_tls_status_update.go b/pkg/deployment/reconcile/action_tls_status_update.go index 247e51c6a..21bb4933c 100644 --- a/pkg/deployment/reconcile/action_tls_status_update.go +++ b/pkg/deployment/reconcile/action_tls_status_update.go @@ -65,19 +65,20 @@ func (a *tlsKeyStatusUpdateAction) Start(ctx context.Context) (bool, error) { keyHashes := secretKeysToListWithPrefix("sha256:", f) if err = a.actionCtx.WithStatusUpdate(func(s *api.DeploymentStatus) bool { + r := false if len(keyHashes) == 1 { if s.Hashes.TLS.CA == nil || *s.Hashes.TLS.CA != keyHashes[0] { s.Hashes.TLS.CA = util.NewString(keyHashes[0]) - return true + r = true } } if !util.CompareStringArray(keyHashes, s.Hashes.TLS.Truststore) { s.Hashes.TLS.Truststore = keyHashes - return true + r = true } - return false + return r }); err != nil { return false, err } diff --git a/pkg/deployment/reconcile/plan_builder_tls.go b/pkg/deployment/reconcile/plan_builder_tls.go index 51c9439fd..306be0ba2 100644 --- a/pkg/deployment/reconcile/plan_builder_tls.go +++ b/pkg/deployment/reconcile/plan_builder_tls.go @@ -23,11 +23,9 @@ package reconcile import ( - "bytes" "context" "crypto/tls" "crypto/x509" - "encoding/pem" "fmt" "net/http" "net/url" @@ -37,129 +35,16 @@ import ( "github.com/arangodb/kube-arangodb/pkg/deployment/client" "github.com/arangodb/kube-arangodb/pkg/util/constants" - "github.com/arangodb-helper/go-certificates" + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/arangodb/kube-arangodb/pkg/deployment/resources" "github.com/arangodb/kube-arangodb/pkg/deployment/resources/inspector" "github.com/arangodb/kube-arangodb/pkg/util" - "github.com/pkg/errors" - core "k8s.io/api/core/v1" - - api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/rs/zerolog" ) const CertificateRenewalMargin = 7 * 24 * time.Hour -type Certificates []*x509.Certificate - -func (c Certificates) Contains(cert *x509.Certificate) bool { - for _, localCert := range c { - if !localCert.Equal(cert) { - return false - } - } - - return true -} - -func (c Certificates) ContainsAll(certs Certificates) bool { - if len(certs) == 0 { - return true - } - - for _, cert := range certs { - if !c.Contains(cert) { - return false - } - } - - return true -} - -func (c Certificates) ToPem() ([]byte, error) { - bytes := bytes.NewBuffer([]byte{}) - - for _, cert := range c { - if err := pem.Encode(bytes, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil { - return nil, err - } - } - - return bytes.Bytes(), nil -} - -func (c Certificates) AsCertPool() *x509.CertPool { - cp := x509.NewCertPool() - - for _, cert := range c { - cp.AddCert(cert) - } - - return cp -} - -func getCertsFromData(log zerolog.Logger, caPem []byte) Certificates { - certs := make([]*x509.Certificate, 0, 2) - - for { - pem, rest := pem.Decode(caPem) - if pem == nil { - break - } - - caPem = rest - - cert, err := x509.ParseCertificate(pem.Bytes) - if err != nil { - // This error should be ignored - log.Error().Err(err).Msg("Unable to parse certificate") - continue - } - - certs = append(certs, cert) - } - - return certs -} - -func getCertsFromSecret(log zerolog.Logger, secret *core.Secret) Certificates { - caPem, exists := secret.Data[core.ServiceAccountRootCAKey] - if !exists { - return nil - } - - return getCertsFromData(log, caPem) -} - -func getKeyCertFromCache(log zerolog.Logger, cachedStatus inspector.Inspector, spec api.DeploymentSpec, certName, keyName string) (Certificates, interface{}, error) { - caSecret, exists := cachedStatus.Secret(spec.TLS.GetCASecretName()) - if !exists { - return nil, nil, errors.Errorf("CA Secret does not exists") - } - - return getKeyCertFromSecret(log, caSecret, keyName, certName) -} - -func getKeyCertFromSecret(log zerolog.Logger, secret *core.Secret, certName, keyName string) (Certificates, interface{}, error) { - ca, exists := secret.Data[certName] - if !exists { - return nil, nil, errors.Errorf("Key %s missing in secret", certName) - } - - key, exists := secret.Data[keyName] - if !exists { - return nil, nil, errors.Errorf("Key %s missing in secret", keyName) - } - - cert, keys, err := certificates.LoadFromPEM(string(ca), string(key)) - if err != nil { - return nil, nil, err - } - - return cert, keys, nil -} - // createTLSStatusUpdate creates plan to update ca info func createTLSStatusUpdate(ctx context.Context, log zerolog.Logger, apiObject k8sutil.APIObject, @@ -228,7 +113,7 @@ func createCAAppendPlan(ctx context.Context, return nil } - ca, _, err := getKeyCertFromSecret(log, caSecret, resources.CACertName, resources.CAKeyName) + ca, _, err := resources.GetKeyCertFromSecret(log, caSecret, resources.CACertName, resources.CAKeyName) if err != nil { log.Warn().Err(err).Str("secret", spec.TLS.GetCASecretName()).Msg("CA Secret does not contains Cert") return nil @@ -281,7 +166,7 @@ func createCARenewalPlan(ctx context.Context, return nil } - cas, _, err := getKeyCertFromSecret(log, caSecret, resources.CACertName, resources.CAKeyName) + cas, _, err := resources.GetKeyCertFromSecret(log, caSecret, resources.CACertName, resources.CAKeyName) if err != nil { log.Warn().Err(err).Str("secret", spec.TLS.GetCASecretName()).Msg("CA Secret does not contains Cert") return nil @@ -312,7 +197,7 @@ func createCACleanPlan(ctx context.Context, return nil } - ca, _, err := getKeyCertFromSecret(log, caSecret, resources.CACertName, resources.CAKeyName) + ca, _, err := resources.GetKeyCertFromSecret(log, caSecret, resources.CACertName, resources.CAKeyName) if err != nil { log.Warn().Err(err).Str("secret", spec.TLS.GetCASecretName()).Msg("CA Secret does not contains Cert") return nil @@ -461,7 +346,7 @@ func createKeyfileRenewalPlanMode( return mode } -func checkServerValidCertRequest(ctx context.Context, apiObject k8sutil.APIObject, group api.ServerGroup, member api.MemberStatus, ca Certificates) (*tls.ConnectionState, error) { +func checkServerValidCertRequest(ctx context.Context, apiObject k8sutil.APIObject, group api.ServerGroup, member api.MemberStatus, ca resources.Certificates) (*tls.ConnectionState, error) { endpoint := fmt.Sprintf("https://%s:%d", k8sutil.CreatePodDNSName(apiObject, group.AsRole(), member.ID), k8sutil.ArangoPort) tlsConfig := &tls.Config{ @@ -493,7 +378,7 @@ func keyfileRenewalRequired(ctx context.Context, return false, false } - ca, _, err := getKeyCertFromSecret(log, caSecret, resources.CACertName, resources.CAKeyName) + ca, _, err := resources.GetKeyCertFromSecret(log, caSecret, resources.CACertName, resources.CAKeyName) if err != nil { log.Warn().Err(err).Str("secret", spec.TLS.GetCASecretName()).Msg("CA Secret does not contains Cert") return false, false diff --git a/pkg/deployment/resources/certicicates.go b/pkg/deployment/resources/certicicates.go new file mode 100644 index 000000000..66b5ef7f6 --- /dev/null +++ b/pkg/deployment/resources/certicicates.go @@ -0,0 +1,145 @@ +// +// DISCLAIMER +// +// Copyright 2020 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// +// Author Adam Janikowski +// + +package resources + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + + "github.com/arangodb-helper/go-certificates" + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" + "github.com/arangodb/kube-arangodb/pkg/deployment/resources/inspector" + "github.com/pkg/errors" + "github.com/rs/zerolog" + core "k8s.io/api/core/v1" +) + +type Certificates []*x509.Certificate + +func (c Certificates) Contains(cert *x509.Certificate) bool { + for _, localCert := range c { + if !localCert.Equal(cert) { + return false + } + } + + return true +} + +func (c Certificates) ContainsAll(certs Certificates) bool { + if len(certs) == 0 { + return true + } + + for _, cert := range certs { + if !c.Contains(cert) { + return false + } + } + + return true +} + +func (c Certificates) ToPem() ([]byte, error) { + bytes := bytes.NewBuffer([]byte{}) + + for _, cert := range c { + if err := pem.Encode(bytes, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil { + return nil, err + } + } + + return bytes.Bytes(), nil +} + +func (c Certificates) AsCertPool() *x509.CertPool { + cp := x509.NewCertPool() + + for _, cert := range c { + cp.AddCert(cert) + } + + return cp +} + +func GetCertsFromData(log zerolog.Logger, caPem []byte) Certificates { + certs := make([]*x509.Certificate, 0, 2) + + for { + pem, rest := pem.Decode(caPem) + if pem == nil { + break + } + + caPem = rest + + cert, err := x509.ParseCertificate(pem.Bytes) + if err != nil { + // This error should be ignored + log.Error().Err(err).Msg("Unable to parse certificate") + continue + } + + certs = append(certs, cert) + } + + return certs +} + +func GetCertsFromSecret(log zerolog.Logger, secret *core.Secret) Certificates { + caPem, exists := secret.Data[core.ServiceAccountRootCAKey] + if !exists { + return nil + } + + return GetCertsFromData(log, caPem) +} + +func GetKeyCertFromCache(log zerolog.Logger, cachedStatus inspector.Inspector, spec api.DeploymentSpec, certName, keyName string) (Certificates, interface{}, error) { + caSecret, exists := cachedStatus.Secret(spec.TLS.GetCASecretName()) + if !exists { + return nil, nil, errors.Errorf("CA Secret does not exists") + } + + return GetKeyCertFromSecret(log, caSecret, keyName, certName) +} + +func GetKeyCertFromSecret(log zerolog.Logger, secret *core.Secret, certName, keyName string) (Certificates, interface{}, error) { + ca, exists := secret.Data[certName] + if !exists { + return nil, nil, errors.Errorf("Key %s missing in secret", certName) + } + + key, exists := secret.Data[keyName] + if !exists { + return nil, nil, errors.Errorf("Key %s missing in secret", keyName) + } + + cert, keys, err := certificates.LoadFromPEM(string(ca), string(key)) + if err != nil { + return nil, nil, err + } + + return cert, keys, nil +} diff --git a/pkg/deployment/resources/inspector/inspector.go b/pkg/deployment/resources/inspector/inspector.go index 88e10b8f9..d4952c14a 100644 --- a/pkg/deployment/resources/inspector/inspector.go +++ b/pkg/deployment/resources/inspector/inspector.go @@ -23,6 +23,8 @@ package inspector import ( + "sync" + core "k8s.io/api/core/v1" policy "k8s.io/api/policy/v1beta1" "k8s.io/client-go/kubernetes" @@ -83,6 +85,8 @@ func NewInspectorFromData(pods map[string]*core.Pod, } type Inspector interface { + Refresh(k kubernetes.Interface, namespace string) error + Pod(name string) (*core.Pod, bool) IteratePods(action PodAction, filters ...PodFilter) error @@ -103,6 +107,8 @@ type Inspector interface { } type inspector struct { + lock sync.Mutex + pods map[string]*core.Pod secrets map[string]*core.Secret pvcs map[string]*core.PersistentVolumeClaim @@ -110,3 +116,47 @@ type inspector struct { serviceAccounts map[string]*core.ServiceAccount podDisruptionBudgets map[string]*policy.PodDisruptionBudget } + +func (i *inspector) Refresh(k kubernetes.Interface, namespace string) error { + i.lock.Lock() + defer i.lock.Unlock() + + pods, err := podsToMap(k, namespace) + if err != nil { + return err + } + + secrets, err := secretsToMap(k, namespace) + if err != nil { + return err + } + + pvcs, err := pvcsToMap(k, namespace) + if err != nil { + return err + } + + services, err := servicesToMap(k, namespace) + if err != nil { + return err + } + + serviceAccounts, err := serviceAccountsToMap(k, namespace) + if err != nil { + return err + } + + podDisruptionBudgets, err := podDisruptionBudgetsToMap(k, namespace) + if err != nil { + return err + } + + i.pods = pods + i.secrets = secrets + i.pvcs = pvcs + i.services = services + i.serviceAccounts = serviceAccounts + i.podDisruptionBudgets = podDisruptionBudgets + + return nil +} diff --git a/pkg/deployment/resources/inspector/pdbs.go b/pkg/deployment/resources/inspector/pdbs.go index a3945e80e..dea6b8069 100644 --- a/pkg/deployment/resources/inspector/pdbs.go +++ b/pkg/deployment/resources/inspector/pdbs.go @@ -33,6 +33,9 @@ type PodDisruptionBudgetFilter func(podDisruptionBudget *policy.PodDisruptionBud type PodDisruptionBudgetAction func(podDisruptionBudget *policy.PodDisruptionBudget) error func (i *inspector) IteratePodDisruptionBudgets(action PodDisruptionBudgetAction, filters ...PodDisruptionBudgetFilter) error { + i.lock.Lock() + defer i.lock.Unlock() + for _, podDisruptionBudget := range i.podDisruptionBudgets { if err := i.iteratePodDisruptionBudget(podDisruptionBudget, action, filters...); err != nil { return err @@ -52,6 +55,9 @@ func (i *inspector) iteratePodDisruptionBudget(podDisruptionBudget *policy.PodDi } func (i *inspector) PodDisruptionBudget(name string) (*policy.PodDisruptionBudget, bool) { + i.lock.Lock() + defer i.lock.Unlock() + podDisruptionBudget, ok := i.podDisruptionBudgets[name] if !ok { return nil, false diff --git a/pkg/deployment/resources/inspector/pods.go b/pkg/deployment/resources/inspector/pods.go index 70a5bcd6e..ad0be9ba1 100644 --- a/pkg/deployment/resources/inspector/pods.go +++ b/pkg/deployment/resources/inspector/pods.go @@ -33,6 +33,9 @@ type PodFilter func(pod *core.Pod) bool type PodAction func(pod *core.Pod) error func (i *inspector) IteratePods(action PodAction, filters ...PodFilter) error { + i.lock.Lock() + defer i.lock.Unlock() + for _, pod := range i.pods { if err := i.iteratePod(pod, action, filters...); err != nil { return err @@ -52,6 +55,9 @@ func (i *inspector) iteratePod(pod *core.Pod, action PodAction, filters ...PodFi } func (i *inspector) Pod(name string) (*core.Pod, bool) { + i.lock.Lock() + defer i.lock.Unlock() + pod, ok := i.pods[name] if !ok { return nil, false diff --git a/pkg/deployment/resources/inspector/pvcs.go b/pkg/deployment/resources/inspector/pvcs.go index 1f97e004f..f805bea9f 100644 --- a/pkg/deployment/resources/inspector/pvcs.go +++ b/pkg/deployment/resources/inspector/pvcs.go @@ -33,6 +33,9 @@ type PersistentVolumeClaimFilter func(pvc *core.PersistentVolumeClaim) bool type PersistentVolumeClaimAction func(pvc *core.PersistentVolumeClaim) error func (i *inspector) IteratePersistentVolumeClaims(action PersistentVolumeClaimAction, filters ...PersistentVolumeClaimFilter) error { + i.lock.Lock() + defer i.lock.Unlock() + for _, pvc := range i.pvcs { if err := i.iteratePersistentVolumeClaim(pvc, action, filters...); err != nil { return err @@ -52,6 +55,9 @@ func (i *inspector) iteratePersistentVolumeClaim(pvc *core.PersistentVolumeClaim } func (i *inspector) PersistentVolumeClaim(name string) (*core.PersistentVolumeClaim, bool) { + i.lock.Lock() + defer i.lock.Unlock() + pvc, ok := i.pvcs[name] if !ok { return nil, false diff --git a/pkg/deployment/resources/inspector/sa.go b/pkg/deployment/resources/inspector/sa.go index 59038eeef..d41016ee5 100644 --- a/pkg/deployment/resources/inspector/sa.go +++ b/pkg/deployment/resources/inspector/sa.go @@ -33,6 +33,9 @@ type ServiceAccountFilter func(serviceAccount *core.ServiceAccount) bool type ServiceAccountAction func(serviceAccount *core.ServiceAccount) error func (i *inspector) IterateServiceAccounts(action ServiceAccountAction, filters ...ServiceAccountFilter) error { + i.lock.Lock() + defer i.lock.Unlock() + for _, serviceAccount := range i.serviceAccounts { if err := i.iterateServiceAccount(serviceAccount, action, filters...); err != nil { return err @@ -52,6 +55,9 @@ func (i *inspector) iterateServiceAccount(serviceAccount *core.ServiceAccount, a } func (i *inspector) ServiceAccount(name string) (*core.ServiceAccount, bool) { + i.lock.Lock() + defer i.lock.Unlock() + serviceAccount, ok := i.serviceAccounts[name] if !ok { return nil, false diff --git a/pkg/deployment/resources/inspector/secrets.go b/pkg/deployment/resources/inspector/secrets.go index 913562028..6bfab874f 100644 --- a/pkg/deployment/resources/inspector/secrets.go +++ b/pkg/deployment/resources/inspector/secrets.go @@ -33,6 +33,9 @@ type SecretFilter func(pod *core.Secret) bool type SecretAction func(pod *core.Secret) error func (i *inspector) IterateSecrets(action SecretAction, filters ...SecretFilter) error { + i.lock.Lock() + defer i.lock.Unlock() + for _, secret := range i.secrets { if err := i.iterateSecrets(secret, action, filters...); err != nil { return err @@ -52,6 +55,9 @@ func (i *inspector) iterateSecrets(secret *core.Secret, action SecretAction, fil } func (i *inspector) Secret(name string) (*core.Secret, bool) { + i.lock.Lock() + defer i.lock.Unlock() + secret, ok := i.secrets[name] if !ok { return nil, false diff --git a/pkg/deployment/resources/inspector/services.go b/pkg/deployment/resources/inspector/services.go index 1dccd5171..46fa52ae5 100644 --- a/pkg/deployment/resources/inspector/services.go +++ b/pkg/deployment/resources/inspector/services.go @@ -33,6 +33,9 @@ type ServiceFilter func(pod *core.Service) bool type ServiceAction func(pod *core.Service) error func (i *inspector) IterateServices(action ServiceAction, filters ...ServiceFilter) error { + i.lock.Lock() + defer i.lock.Unlock() + for _, service := range i.services { if err := i.iterateServices(service, action, filters...); err != nil { return err @@ -52,6 +55,9 @@ func (i *inspector) iterateServices(service *core.Service, action ServiceAction, } func (i *inspector) Service(name string) (*core.Service, bool) { + i.lock.Lock() + defer i.lock.Unlock() + service, ok := i.services[name] if !ok { return nil, false diff --git a/pkg/deployment/resources/secrets.go b/pkg/deployment/resources/secrets.go index a84f0b6b1..c4bbf4f98 100644 --- a/pkg/deployment/resources/secrets.go +++ b/pkg/deployment/resources/secrets.go @@ -83,31 +83,31 @@ func (r *Resources) EnsureSecrets(log zerolog.Logger, cachedStatus inspector.Ins if spec.IsAuthenticated() { counterMetric.Inc() - if err := r.ensureTokenSecret(cachedStatus, secrets, spec.Authentication.GetJWTSecretName()); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureTokenSecret(cachedStatus, secrets, spec.Authentication.GetJWTSecretName())); err != nil { return maskAny(err) } if imageFound { if pod.VersionHasJWTSecretKeyfolder(image.ArangoDBVersion, image.Enterprise) { - if err := r.ensureTokenSecretFolder(cachedStatus, secrets, spec.Authentication.GetJWTSecretName(), pod.JWTSecretFolder(deploymentName)); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureTokenSecretFolder(cachedStatus, secrets, spec.Authentication.GetJWTSecretName(), pod.JWTSecretFolder(deploymentName))); err != nil { return maskAny(err) } } } if spec.Metrics.IsEnabled() { - if err := r.ensureExporterTokenSecret(cachedStatus, secrets, spec.Metrics.GetJWTTokenSecretName(), spec.Authentication.GetJWTSecretName()); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureExporterTokenSecret(cachedStatus, secrets, spec.Metrics.GetJWTTokenSecretName(), spec.Authentication.GetJWTSecretName())); err != nil { return maskAny(err) } } } if spec.IsSecure() { counterMetric.Inc() - if err := r.ensureTLSCACertificateSecret(cachedStatus, secrets, spec.TLS); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureTLSCACertificateSecret(cachedStatus, secrets, spec.TLS)); err != nil { return maskAny(err) } - if err := r.ensureSecretWithEmptyKey(cachedStatus, secrets, GetCASecretName(r.context.GetAPIObject()), "empty"); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureSecretWithEmptyKey(cachedStatus, secrets, GetCASecretName(r.context.GetAPIObject()), "empty")); err != nil { return maskAny(err) } @@ -129,11 +129,13 @@ func (r *Resources) EnsureSecrets(log zerolog.Logger, cachedStatus inspector.Ins serverNames = append(serverNames, ip) } owner := apiObject.AsOwner() - if err := createTLSServerCertificate(log, secrets, serverNames, spec.TLS, tlsKeyfileSecretName, &owner); err != nil && !k8sutil.IsAlreadyExists(err) { + if err := r.refreshCache(cachedStatus, createTLSServerCertificate(log, secrets, serverNames, spec.TLS, tlsKeyfileSecretName, &owner)); err != nil && !k8sutil.IsAlreadyExists(err) { return maskAny(errors.Wrapf(err, "Failed to create TLS keyfile secret")) } - return operatorErrors.Reconcile() + if err := r.refreshCache(cachedStatus, operatorErrors.Reconcile()); err != nil { + return maskAny(err) + } } } return nil @@ -143,32 +145,48 @@ func (r *Resources) EnsureSecrets(log zerolog.Logger, cachedStatus inspector.Ins } if spec.RocksDB.IsEncrypted() { if i := status.CurrentImage; i != nil && i.Enterprise && i.ArangoDBVersion.CompareTo("3.7.0") >= 0 { - if err := r.ensureEncryptionKeyfolderSecret(cachedStatus, secrets, spec.RocksDB.Encryption.GetKeySecretName(), pod.GetEncryptionFolderSecretName(deploymentName)); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureEncryptionKeyfolderSecret(cachedStatus, secrets, spec.RocksDB.Encryption.GetKeySecretName(), pod.GetEncryptionFolderSecretName(deploymentName))); err != nil { return maskAny(err) } } } if spec.Sync.IsEnabled() { counterMetric.Inc() - if err := r.ensureTokenSecret(cachedStatus, secrets, spec.Sync.Authentication.GetJWTSecretName()); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureTokenSecret(cachedStatus, secrets, spec.Sync.Authentication.GetJWTSecretName())); err != nil { return maskAny(err) } counterMetric.Inc() - if err := r.ensureTokenSecret(cachedStatus, secrets, spec.Sync.Monitoring.GetTokenSecretName()); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureTokenSecret(cachedStatus, secrets, spec.Sync.Monitoring.GetTokenSecretName())); err != nil { return maskAny(err) } counterMetric.Inc() - if err := r.ensureTLSCACertificateSecret(cachedStatus, secrets, spec.Sync.TLS); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureTLSCACertificateSecret(cachedStatus, secrets, spec.Sync.TLS)); err != nil { return maskAny(err) } counterMetric.Inc() - if err := r.ensureClientAuthCACertificateSecret(cachedStatus, secrets, spec.Sync.Authentication); err != nil { + if err := r.refreshCache(cachedStatus, r.ensureClientAuthCACertificateSecret(cachedStatus, secrets, spec.Sync.Authentication)); err != nil { return maskAny(err) } } return nil } +func (r *Resources) refreshCache(cachedStatus inspector.Inspector, err error) error { + if err == nil { + return nil + } + + if operatorErrors.IsReconcile(err) { + if err := cachedStatus.Refresh(r.context.GetKubeCli(), r.context.GetNamespace()); err != nil { + return maskAny(err) + } + } else { + return maskAny(err) + } + + return nil +} + func (r *Resources) ensureTokenSecretFolder(cachedStatus inspector.Inspector, secrets k8sutil.SecretInterface, secretName, folderSecretName string) error { if _, exists := cachedStatus.Secret(folderSecretName); exists { return nil @@ -184,7 +202,10 @@ func (r *Resources) ensureTokenSecretFolder(cachedStatus inspector.Inspector, se return errors.Errorf("Token secret is invalid") } - if err := r.createSecretWithKey(secrets, folderSecretName, util.SHA256(token), token); err != nil { + if err := r.createSecretWithMod(secrets, folderSecretName, func(s *core.Secret) { + s.Data[util.SHA256(token)] = token + s.Data[pod.ActiveJWTKey] = token + }); err != nil { return err } @@ -241,19 +262,20 @@ func (r *Resources) ensureSecretWithKey(cachedStatus inspector.Inspector, secret return nil } -func (r *Resources) createSecretWithKey(secrets k8sutil.SecretInterface, secretName, keyName string, value []byte) error { +func (r *Resources) createSecretWithMod(secrets k8sutil.SecretInterface, secretName string, f func(s *core.Secret)) error { // Create secret secret := &core.Secret{ ObjectMeta: meta.ObjectMeta{ Name: secretName, }, - Data: map[string][]byte{ - keyName: value, - }, + Data: map[string][]byte{}, } // Attach secret to owner owner := r.context.GetAPIObject().AsOwner() k8sutil.AddOwnerRefToObject(secret, &owner) + + f(secret) + if _, err := secrets.Create(secret); err != nil { // Failed to create secret return maskAny(err) @@ -262,6 +284,12 @@ func (r *Resources) createSecretWithKey(secrets k8sutil.SecretInterface, secretN return operatorErrors.Reconcile() } +func (r *Resources) createSecretWithKey(secrets k8sutil.SecretInterface, secretName, keyName string, value []byte) error { + return r.createSecretWithMod(secrets, secretName, func(s *core.Secret) { + s.Data[keyName] = value + }) +} + func (r *Resources) createTokenSecret(secrets k8sutil.SecretInterface, secretName string) error { tokenData := make([]byte, 32) rand.Read(tokenData) @@ -432,6 +460,43 @@ func (r *Resources) ensureTLSCACertificateSecret(cachedStatus inspector.Inspecto return nil } +// ensureTLSCACertificateSecret checks if a secret with given name exists in the namespace +// of the deployment. If not, it will add such a secret with a generated CA certificate. +func (r *Resources) ensureTLSCAFolderSecret(cachedStatus inspector.Inspector, secrets k8sutil.SecretInterface, spec api.TLSSpec, folderSecretName string) error { + if spec.CASecretName == nil { + return errors.Errorf("CA Secret Name is nil") + } + + caSecret, ok := cachedStatus.Secret(*spec.CASecretName) + if !ok { + return errors.Errorf("CA Secret is missing") + } + + if _, exists := cachedStatus.Secret(spec.GetCASecretName()); !exists { + ca, _, err := GetKeyCertFromSecret(r.log, caSecret, CACertName, CAKeyName) + if err != nil { + return maskAny(err) + } + + if len(ca) == 0 { + return maskAny(err) + } + + caData, err := ca.ToPem() + if err != nil { + return maskAny(err) + } + + certSha := util.SHA256(caData) + + // Secret not found, create it + return r.createSecretWithMod(secrets, folderSecretName, func(s *core.Secret) { + s.Data[certSha] = caData + }) + } + return nil +} + // ensureClientAuthCACertificateSecret checks if a secret with given name exists in the namespace // of the deployment. If not, it will add such a secret with a generated CA certificate. func (r *Resources) ensureClientAuthCACertificateSecret(cachedStatus inspector.Inspector, secrets k8sutil.SecretInterface, spec api.SyncAuthenticationSpec) error {