1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Bugfix] Add ArangoSync TLS based rotation (#996)

This commit is contained in:
jwierzbo 2022-06-08 12:05:16 +02:00 committed by GitHub
parent 98964cb02a
commit c0c98fd6ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 103 additions and 37 deletions

View file

@ -1,6 +1,7 @@
# Change Log
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
- (Feature) Add ArangoSync TLS based rotation
## [1.2.13](https://github.com/arangodb/kube-arangodb/tree/1.2.13) (2022-06-07)
- (Bugfix) Fix arangosync members state inspection

View file

@ -33,6 +33,8 @@ const (
ArangoExporterInternalEndpointV2 = "/_admin/metrics/v2"
ArangoExporterDefaultEndpoint = "/metrics"
ArangoSyncStatusEndpoint = "/_api/version"
// K8s constants
ClusterIPNone = "None"
TopologyKeyHostname = "kubernetes.io/hostname"

View file

@ -30,21 +30,20 @@ import (
"reflect"
"time"
memberTls "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/deployment/client"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
"github.com/arangodb/go-driver"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/actions"
"github.com/arangodb/kube-arangodb/pkg/deployment/client"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
memberTls "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
"github.com/rs/zerolog"
)
@ -286,6 +285,42 @@ func createCACleanPlan(ctx context.Context,
return nil
}
func createKeyfileRenewalPlanSynced(ctx context.Context,
log zerolog.Logger, apiObject k8sutil.APIObject,
spec api.DeploymentSpec, status api.DeploymentStatus,
planCtx PlanBuilderContext) api.Plan {
if !spec.Sync.IsEnabled() || !spec.Sync.TLS.IsSecure() {
return nil
}
var plan api.Plan
group := api.ServerGroupSyncMasters
for _, statusMember := range status.Members.AsListInGroup(group) {
member := statusMember.Member
if !plan.IsEmpty() {
return nil
}
cache, ok := planCtx.ACS().ClusterCache(member.ClusterID)
if !ok {
continue
}
lCtx, c := context.WithTimeout(ctx, 500*time.Millisecond)
defer c()
if renew, _ := keyfileRenewalRequired(lCtx, log, apiObject, spec.Sync.TLS, spec, cache, planCtx, group, member, api.TLSRotateModeRecreate); renew {
log.Info().Msg("Renewal of keyfile required - Recreate (sync master)")
plan = append(plan, tlsRotateConditionAction(group, member.ID, "Restart sync master after keyfile removal"))
}
}
return plan
}
func createKeyfileRenewalPlanDefault(ctx context.Context,
log zerolog.Logger, apiObject k8sutil.APIObject,
spec api.DeploymentSpec, status api.DeploymentStatus,
@ -314,8 +349,8 @@ func createKeyfileRenewalPlanDefault(ctx context.Context,
lCtx, c := context.WithTimeout(ctx, 500*time.Millisecond)
defer c()
if renew, _ := keyfileRenewalRequired(lCtx, log, apiObject, spec, cache, planCtx, group, member, api.TLSRotateModeRecreate); renew {
log.Info().Msg("Renewal of keyfile required - Recreate")
if renew, _ := keyfileRenewalRequired(lCtx, log, apiObject, spec.TLS, spec, cache, planCtx, group, member, api.TLSRotateModeRecreate); renew {
log.Info().Msg("Renewal of keyfile required - Recreate (server)")
plan = append(plan, tlsRotateConditionAction(group, member.ID, "Restart server after keyfile removal"))
}
}
@ -350,8 +385,8 @@ func createKeyfileRenewalPlanInPlace(ctx context.Context,
lCtx, c := context.WithTimeout(ctx, 500*time.Millisecond)
defer c()
if renew, recreate := keyfileRenewalRequired(lCtx, log, apiObject, spec, cache, planCtx, group, member, api.TLSRotateModeInPlace); renew {
log.Info().Msg("Renewal of keyfile required - InPlace")
if renew, recreate := keyfileRenewalRequired(lCtx, log, apiObject, spec.TLS, spec, cache, planCtx, group, member, api.TLSRotateModeInPlace); renew {
log.Info().Msg("Renewal of keyfile required - InPlace (server)")
if recreate {
plan = append(plan, actions.NewAction(api.ActionTypeCleanTLSKeyfileCertificate, group, member, "Remove server keyfile and enforce renewal"))
}
@ -376,12 +411,16 @@ func createKeyfileRenewalPlan(ctx context.Context,
gCtx, c := context.WithTimeout(ctx, 2*time.Second)
defer c()
plan := createKeyfileRenewalPlanSynced(gCtx, log, apiObject, spec, status, planCtx)
switch createKeyfileRenewalPlanMode(spec, status) {
case api.TLSRotateModeInPlace:
return createKeyfileRenewalPlanInPlace(gCtx, log, apiObject, spec, status, planCtx)
plan = append(plan, createKeyfileRenewalPlanInPlace(gCtx, log, apiObject, spec, status, planCtx)...)
default:
return createKeyfileRenewalPlanDefault(gCtx, log, apiObject, spec, status, planCtx)
plan = append(plan, createKeyfileRenewalPlanDefault(gCtx, log, apiObject, spec, status, planCtx)...)
}
return plan
}
func createKeyfileRenewalPlanMode(
@ -419,6 +458,9 @@ func createKeyfileRenewalPlanMode(
func checkServerValidCertRequest(ctx context.Context, context PlanBuilderContext, apiObject k8sutil.APIObject, group api.ServerGroup, member api.MemberStatus, ca resources.Certificates) (*tls.ConnectionState, error) {
endpoint := fmt.Sprintf("https://%s:%d", k8sutil.CreatePodDNSNameWithDomain(apiObject, context.GetSpec().ClusterDomain, group.AsRole(), member.ID), shared.ArangoPort)
if group == api.ServerGroupSyncMasters {
endpoint = fmt.Sprintf("https://%s:%d%s", k8sutil.CreatePodDNSNameWithDomain(apiObject, context.GetSpec().ClusterDomain, group.AsRole(), member.ID), shared.ArangoSyncMasterPort, shared.ArangoSyncStatusEndpoint)
}
tlsConfig := &tls.Config{
RootCAs: ca.AsCertPool(),
@ -452,12 +494,13 @@ func checkServerValidCertRequest(ctx context.Context, context PlanBuilderContext
return resp.TLS, nil
}
// keyfileRenewalRequired checks if a keyfile renewal is required and if recreation should be made
func keyfileRenewalRequired(ctx context.Context,
log zerolog.Logger, apiObject k8sutil.APIObject,
log zerolog.Logger, apiObject k8sutil.APIObject, tls api.TLSSpec,
spec api.DeploymentSpec, cachedStatus inspectorInterface.Inspector,
context PlanBuilderContext,
group api.ServerGroup, member api.MemberStatus, mode api.TLSRotateMode) (bool, bool) {
if !spec.TLS.IsSecure() {
if !tls.IsSecure() {
return false, false
}
@ -469,15 +512,15 @@ func keyfileRenewalRequired(ctx context.Context,
return false, false
}
caSecret, exists := cachedStatus.Secret().V1().GetSimple(spec.TLS.GetCASecretName())
caSecret, exists := cachedStatus.Secret().V1().GetSimple(tls.GetCASecretName())
if !exists {
log.Warn().Str("secret", spec.TLS.GetCASecretName()).Msg("CA Secret does not exists")
log.Warn().Str("secret", tls.GetCASecretName()).Msg("CA Secret does not exists")
return false, false
}
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")
log.Warn().Err(err).Str("secret", tls.GetCASecretName()).Msg("CA Secret does not contains Cert")
return false, false
}
@ -487,13 +530,13 @@ func keyfileRenewalRequired(ctx context.Context,
case *url.Error:
switch v.Err.(type) {
case x509.UnknownAuthorityError, x509.CertificateInvalidError:
log.Debug().Err(v.Err).Str("type", reflect.TypeOf(v.Err).String()).Msg("Validation of server cert failed")
log.Debug().Err(v.Err).Str("type", reflect.TypeOf(v.Err).String()).Msgf("Validation of cert for %s failed, renewal is required", memberName)
return true, true
default:
log.Debug().Err(v.Err).Str("type", reflect.TypeOf(v.Err).String()).Msg("Validation of server cert failed")
log.Debug().Err(v.Err).Str("type", reflect.TypeOf(v.Err).String()).Msgf("Validation of cert for %s failed, but cert looks fine - continuing", memberName)
}
default:
log.Debug().Err(err).Str("type", reflect.TypeOf(err).String()).Msg("Validation of server cert failed")
log.Debug().Err(err).Str("type", reflect.TypeOf(err).String()).Msgf("Validation of cert for %s failed, will try again next time", memberName)
}
return false, false
}
@ -514,7 +557,13 @@ func keyfileRenewalRequired(ctx context.Context,
}
// Verify AltNames
altNames, err := memberTls.GetServerAltNames(apiObject, spec, spec.TLS, service, group, member)
var altNames memberTls.KeyfileInput
if group.IsArangosync() {
altNames, err = memberTls.GetSyncAltNames(apiObject, spec, tls, group, member)
} else {
altNames, err = memberTls.GetServerAltNames(apiObject, spec, tls, service, group, member)
}
if err != nil {
log.Warn().Msg("Unable to render alt names")
return false, false
@ -535,7 +584,7 @@ func keyfileRenewalRequired(ctx context.Context,
}
// Ensure secret is propagated only on 3.7.0+ enterprise and inplace mode
if mode == api.TLSRotateModeInPlace {
if mode == api.TLSRotateModeInPlace && group.IsArangod() {
conn, err := context.GetServerClient(ctx, group, member.ID)
if err != nil {
log.Warn().Err(err).Msg("Unable to get client")

View file

@ -26,7 +26,6 @@ import (
"encoding/json"
"fmt"
"net"
"net/url"
"path/filepath"
"strconv"
"sync"
@ -530,22 +529,11 @@ func (r *Resources) createPodForMember(ctx context.Context, cachedStatus inspect
// Create TLS secret
tlsKeyfileSecretName := k8sutil.CreateTLSKeyfileSecretName(apiObject.GetName(), role, m.ID)
names, err := tls.GetAltNames(spec.Sync.TLS)
names, err := tls.GetSyncAltNames(apiObject, spec, spec.Sync.TLS, group, m)
if err != nil {
return errors.WithStack(errors.Wrapf(err, "Failed to render alt names"))
}
names.AltNames = append(names.AltNames,
k8sutil.CreateSyncMasterClientServiceName(apiObject.GetName()),
k8sutil.CreateSyncMasterClientServiceDNSNameWithDomain(apiObject, spec.ClusterDomain),
k8sutil.CreatePodDNSNameWithDomain(apiObject, spec.ClusterDomain, role, m.ID),
)
masterEndpoint := spec.Sync.ExternalAccess.ResolveMasterEndpoint(k8sutil.CreateSyncMasterClientServiceDNSNameWithDomain(apiObject, spec.ClusterDomain), shared.ArangoSyncMasterPort)
for _, ep := range masterEndpoint {
if u, err := url.Parse(ep); err == nil {
names.AltNames = append(names.AltNames, u.Hostname())
}
}
owner := apiObject.AsOwner()
_, err = createTLSServerCertificate(ctx, log, cachedStatus, cachedStatus.SecretsModInterface().V1(), names, spec.Sync.TLS, tlsKeyfileSecretName, &owner)
if err != nil && !k8sutil.IsAlreadyExists(err) {

View file

@ -21,9 +21,13 @@
package tls
import (
"net/url"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -86,3 +90,25 @@ func GetServerAltNames(deployment meta.Object, spec api.DeploymentSpec, tls api.
return k, nil
}
func GetSyncAltNames(deployment meta.Object, spec api.DeploymentSpec, tls api.TLSSpec, group api.ServerGroup, member api.MemberStatus) (KeyfileInput, error) {
k, err := GetAltNames(tls)
if err != nil {
return k, errors.WithStack(err)
}
k.AltNames = append(k.AltNames,
k8sutil.CreateSyncMasterClientServiceName(deployment.GetName()),
k8sutil.CreateSyncMasterClientServiceDNSNameWithDomain(deployment, spec.ClusterDomain),
k8sutil.CreatePodDNSNameWithDomain(deployment, spec.ClusterDomain, group.AsRole(), member.ID),
)
masterEndpoint := spec.Sync.ExternalAccess.ResolveMasterEndpoint(k8sutil.CreateSyncMasterClientServiceDNSNameWithDomain(deployment, spec.ClusterDomain), shared.ArangoSyncMasterPort)
for _, ep := range masterEndpoint {
if u, err := url.Parse(ep); err == nil {
k.AltNames = append(k.AltNames, u.Hostname())
}
}
return k, nil
}