Skip to content

Commit

Permalink
[release/v0.4] Add a check for the agent-tls-mode setting (#439)
Browse files Browse the repository at this point in the history
* Add a check for the agent-tls-mode setting (#416)

* Keep importing a wrangler v2 package
  • Loading branch information
maxsokolovsky authored Jul 9, 2024
1 parent 4f99003 commit c0ed99b
Show file tree
Hide file tree
Showing 5 changed files with 507 additions and 18 deletions.
6 changes: 6 additions & 0 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ When a Setting is updated, the following checks take place:
- If set, `user-last-login-default` must be a date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`).
- If set, `user-retention-cron` must be a valid standard cron expression (e.g. `0 0 * * 1`).

#### Forbidden - Update

- If `agent-tls-mode` has `default` or `value` updated from `system-store` to `strict`, then all non-local clusters must
have a status condition `AgentTlsStrictCheck` set to `True`, unless the new setting has an overriding
annotation `cattle.io/force=true`.

## UserAttribute

### Validation Checks
Expand Down
6 changes: 6 additions & 0 deletions pkg/resources/management.cattle.io/v3/setting/Setting.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ When a Setting is updated, the following checks take place:
- If set, `delete-inactive-user-after` must be zero or a positive duration (e.g. `240h`).
- If set, `user-last-login-default` must be a date time according to RFC3339 (e.g. `2023-11-29T00:00:00Z`).
- If set, `user-retention-cron` must be a valid standard cron expression (e.g. `0 0 * * 1`).

### Forbidden - Update

- If `agent-tls-mode` has `default` or `value` updated from `system-store` to `strict`, then all non-local clusters must
have a status condition `AgentTlsStrictCheck` set to `True`, unless the new setting has an overriding
annotation `cattle.io/force=true`.
84 changes: 70 additions & 14 deletions pkg/resources/management.cattle.io/v3/setting/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import (
"time"

v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
"github.com/rancher/webhook/pkg/admission"
objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3"
"github.com/robfig/cron"
admissionv1 "k8s.io/api/admission/v1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/trace"

"github.com/rancher/webhook/pkg/admission"
controllerv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3"
objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3"
)

// MinDeleteInactiveUserAfter is the minimum duration for delete-inactive-user-after setting.
Expand All @@ -33,9 +37,11 @@ type Validator struct {
}

// NewValidator returns a new Validator instance.
func NewValidator() *Validator {
func NewValidator(clusterCache controllerv3.ClusterCache) *Validator {
return &Validator{
admitter: admitter{},
admitter: admitter{
clusterCache: clusterCache,
},
}
}

Expand All @@ -61,29 +67,36 @@ func (v *Validator) Admitters() []admission.Admitter {
return []admission.Admitter{&v.admitter}
}

type admitter struct{}
type admitter struct {
clusterCache controllerv3.ClusterCache
}

// Admit handles the webhook admission requests.
func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) {
listTrace := trace.New("userAttributeValidator Admit", trace.Field{Key: "user", Value: request.UserInfo.Username})
defer listTrace.LogIfLong(admission.SlowTraceDuration)

if request.Operation == admissionv1.Create || request.Operation == admissionv1.Update {
setting, err := objectsv3.SettingFromRequest(&request.AdmissionRequest)
if err != nil {
return nil, fmt.Errorf("failed to get Setting from request: %w", err)
oldSetting, newSetting, err := objectsv3.SettingOldAndNewFromRequest(&request.AdmissionRequest)
if err != nil {
return nil, fmt.Errorf("failed to get Setting from request: %w", err)
}
switch request.Operation {
case admissionv1.Create:
if err := a.validateUserRetentionSettings(newSetting); err != nil {
return admission.ResponseBadRequest(err.Error()), nil
}

err = a.validateSetting(setting)
if err != nil {
case admissionv1.Update:
if err := a.validateUserRetentionSettings(newSetting); err != nil {
return admission.ResponseBadRequest(err.Error()), nil
}
if err := a.validateAgentTLSMode(*oldSetting, *newSetting); err != nil {
return admission.ResponseBadRequest(err.Error()), nil
}
}

return admission.ResponseAllowed(), nil
}

func (a *admitter) validateSetting(s *v3.Setting) error {
func (a *admitter) validateUserRetentionSettings(s *v3.Setting) error {
var err error

switch s.Name {
Expand Down Expand Up @@ -117,6 +130,31 @@ func (a *admitter) validateSetting(s *v3.Setting) error {
return nil
}

func (a *admitter) validateAgentTLSMode(oldSetting, newSetting v3.Setting) error {
if oldSetting.Name != "agent-tls-mode" || newSetting.Name != "agent-tls-mode" {
return nil
}
if effectiveValue(oldSetting) == "system-store" && effectiveValue(newSetting) == "strict" {
if _, force := newSetting.Annotations["cattle.io/force"]; force {
return nil
}
clusters, err := a.clusterCache.List(labels.NewSelector())
if err != nil {
return fmt.Errorf("failed to list clusters: %w", err)
}
for _, cluster := range clusters {
if cluster.Name == "local" {
continue
}
if !clusterConditionMatches(cluster, "AgentTlsStrictCheck", "True") {
return field.Forbidden(field.NewPath("value", "default"),
fmt.Sprintf("AgentTlsStrictCheck condition of cluster %s isn't 'True'", cluster.Name))
}
}
}
return nil
}

func validateDuration(value string) (time.Duration, error) {
dur, err := time.ParseDuration(value)
if err != nil {
Expand All @@ -129,3 +167,21 @@ func validateDuration(value string) (time.Duration, error) {

return dur, err
}

func clusterConditionMatches(cluster *v3.Cluster, t v3.ClusterConditionType, status v1.ConditionStatus) bool {
for _, cond := range cluster.Status.Conditions {
if cond.Type == t && cond.Status == status {
return true
}
}
return false
}

func effectiveValue(s v3.Setting) string {
if s.Value != "" {
return s.Value
} else if s.Default != "" {
return s.Default
}
return ""
}
Loading

0 comments on commit c0ed99b

Please sign in to comment.