Skip to content

Commit

Permalink
fix: wait for time sync before generating Kubernetes certificates
Browse files Browse the repository at this point in the history
Certificate generation depends on current time, and this bug is visible
on RPi which doesn't have RTC clock - controllers can generate certs
before `timed` does its initial sync creating certs which are not
usable.

Fix generates new intermediate resource `TimeSync` which tracks time
sync status (aggregates `timed` service status and `timed`
enabled/disabled in the config).

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
  • Loading branch information
smira authored and talos-bot committed Feb 9, 2021
1 parent b526c2c commit 85ae9f7
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 0 deletions.
20 changes: 20 additions & 0 deletions internal/app/machined/pkg/controllers/secrets/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ func (ctrl *KubernetesController) Run(ctx context.Context, r controller.Runtime,
ID: pointer.ToString("networkd"),
Kind: controller.DependencyWeak,
},
{
Namespace: v1alpha1.NamespaceName,
Type: v1alpha1.TimeSyncType,
ID: pointer.ToString(v1alpha1.TimeSyncID),
Kind: controller.DependencyWeak,
},
{
Namespace: config.NamespaceName,
Type: config.MachineTypeType,
Expand Down Expand Up @@ -124,6 +130,20 @@ func (ctrl *KubernetesController) Run(ctx context.Context, r controller.Runtime,
continue
}

// wait for time sync as certs depend on current time
timeSyncResource, err := r.Get(ctx, resource.NewMetadata(v1alpha1.NamespaceName, v1alpha1.TimeSyncType, v1alpha1.TimeSyncID, resource.VersionUndefined))
if err != nil {
if state.IsNotFoundError(err) {
continue
}

return err
}

if !timeSyncResource.(*v1alpha1.TimeSync).Sync() {
continue
}

if err = r.Update(ctx, secrets.NewKubernetes(), func(r resource.Resource) error {
k8sSecrets := r.(*secrets.Kubernetes) //nolint: errcheck

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,7 @@ func (ctrl *BootstrapStatusController) readInitialized(ctx context.Context, r co
// wait for key change or any other event in etcd
<-watchCh

r.QueueReconcile()

return nil
}
107 changes: 107 additions & 0 deletions internal/app/machined/pkg/controllers/v1alpha1/time_sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package v1alpha1

import (
"context"
"fmt"
"log"

"github.com/AlekSi/pointer"
"github.com/talos-systems/os-runtime/pkg/controller"
"github.com/talos-systems/os-runtime/pkg/resource"
"github.com/talos-systems/os-runtime/pkg/state"

"github.com/talos-systems/talos/internal/app/machined/pkg/resources/config"
"github.com/talos-systems/talos/internal/app/machined/pkg/resources/v1alpha1"
v1alpha1runtime "github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
)

// TimeSyncController manages v1alpha1.TimeSync based on configuration and service 'timed' status.
type TimeSyncController struct {
V1Alpha1State v1alpha1runtime.State
}

// Name implements controller.Controller interface.
func (ctrl *TimeSyncController) Name() string {
return "v1alpha1.TimeSyncController"
}

// ManagedResources implements controller.Controller interface.
func (ctrl *TimeSyncController) ManagedResources() (resource.Namespace, resource.Type) {
return v1alpha1.NamespaceName, v1alpha1.TimeSyncType
}

// Run implements controller.Controller interface.
//
//nolint: gocyclo
func (ctrl *TimeSyncController) Run(ctx context.Context, r controller.Runtime, logger *log.Logger) error {
if err := r.UpdateDependencies([]controller.Dependency{
{
Namespace: config.NamespaceName,
Type: config.V1Alpha1Type,
ID: pointer.ToString(config.V1Alpha1ID),
Kind: controller.DependencyWeak,
},
{
Namespace: v1alpha1.NamespaceName,
Type: v1alpha1.ServiceType,
ID: pointer.ToString("timed"),
Kind: controller.DependencyWeak,
},
}); err != nil {
return fmt.Errorf("error setting up dependencies: %w", err)
}

for {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
}

cfg, err := r.Get(ctx, resource.NewMetadata(config.NamespaceName, config.V1Alpha1Type, config.V1Alpha1ID, resource.VersionUndefined))
if err != nil {
if state.IsNotFoundError(err) {
continue
}

return fmt.Errorf("error getting config: %w", err)
}

var inSync bool

if cfg.(*config.V1Alpha1).Config().Machine().Time().Disabled() {
// if timed is disabled, time is always "in sync"
inSync = true
}

if ctrl.V1Alpha1State.Platform().Mode() == v1alpha1runtime.ModeContainer {
// container mode skips timed
inSync = true
}

if !inSync {
var timedResource resource.Resource

timedResource, err = r.Get(ctx, resource.NewMetadata(v1alpha1.NamespaceName, v1alpha1.ServiceType, "timed", resource.VersionUndefined))
if err != nil {
if !state.IsNotFoundError(err) {
return err
}
} else {
inSync = timedResource.(*v1alpha1.Service).Healthy()
}
}

if err = r.Update(ctx, v1alpha1.NewTimeSync(), func(r resource.Resource) error {
r.(*v1alpha1.TimeSync).SetSync(inSync)

return nil
}); err != nil {
return fmt.Errorf("error updating objects: %w", err)
}
}
}
82 changes: 82 additions & 0 deletions internal/app/machined/pkg/resources/v1alpha1/time_sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package v1alpha1

import (
"fmt"

"github.com/talos-systems/os-runtime/pkg/resource"
"github.com/talos-systems/os-runtime/pkg/resource/core"
)

// TimeSyncType is type of TimeSync resource.
const TimeSyncType = resource.Type("v1alpha1/timeSync")

// TimeSyncID is the ID of the singletone resource.
const TimeSyncID = resource.ID("timeSync")

// TimeSync describes running current time sync status.
type TimeSync struct {
md resource.Metadata
spec TimeSyncSpec
}

// TimeSyncSpec describes time sync state.
type TimeSyncSpec struct {
Sync bool `yaml:"sync"`
}

// NewTimeSync initializes a TimeSync resource.
func NewTimeSync() *TimeSync {
r := &TimeSync{
md: resource.NewMetadata(NamespaceName, TimeSyncType, TimeSyncID, resource.VersionUndefined),
spec: TimeSyncSpec{},
}

r.md.BumpVersion()

return r
}

// Metadata implements resource.Resource.
func (r *TimeSync) Metadata() *resource.Metadata {
return &r.md
}

// Spec implements resource.Resource.
func (r *TimeSync) Spec() interface{} {
return r.spec
}

func (r *TimeSync) String() string {
return fmt.Sprintf("v1alpha1.TimeSync(%q)", r.md.ID())
}

// DeepCopy implements resource.Resource.
func (r *TimeSync) DeepCopy() resource.Resource {
return &TimeSync{
md: r.md,
spec: r.spec,
}
}

// ResourceDefinition implements core.ResourceDefinitionProvider interface.
func (r *TimeSync) ResourceDefinition() core.ResourceDefinitionSpec {
return core.ResourceDefinitionSpec{
Type: TimeSyncType,
Aliases: []resource.Type{"timeSync"},
DefaultNamespace: NamespaceName,
}
}

// SetSync changes .spec.sync.
func (r *TimeSync) SetSync(sync bool) {
r.spec.Sync = sync
}

// Sync returns .spec.sync.
func (r *TimeSync) Sync() bool {
return r.spec.Sync
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ func (ctrl *Controller) Run(ctx context.Context) error {
// V1Events
V1Alpha1Events: ctrl.v1alpha1Runtime.Events(),
},
&v1alpha1.TimeSyncController{
V1Alpha1State: ctrl.v1alpha1Runtime.State(),
},
&config.MachineTypeController{},
&config.K8sControlPlaneController{},
&k8s.ControlPlaneStaticPodController{},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func NewState() (*State, error) {
for _, r := range []resource.Resource{
&v1alpha1.BootstrapStatus{},
&v1alpha1.Service{},
&v1alpha1.TimeSync{},
&config.V1Alpha1{},
&config.MachineType{},
&config.K8sControlPlane{},
Expand Down

0 comments on commit 85ae9f7

Please sign in to comment.