1
0
mirror of https://github.com/rancher/rke.git synced 2025-09-02 15:34:36 +00:00

add the support for PodSecurity on cluster at least v1.23

This commit is contained in:
Jiaqi Luo
2022-11-01 22:05:35 -07:00
parent f5e18110b6
commit 5fcf75db40
11 changed files with 290 additions and 88 deletions

View File

@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"net"
"os"
"reflect"
@@ -31,6 +30,7 @@ import (
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
apiserverv1 "k8s.io/apiserver/pkg/apis/apiserver/v1"

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
"strings"
"github.com/blang/semver"
@@ -25,6 +24,9 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
apiserverv1 "k8s.io/apiserver/pkg/apis/apiserver/v1"
auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
admissionapiv1 "k8s.io/pod-security-admission/admission/api/v1"
admissionapiv1beta1 "k8s.io/pod-security-admission/admission/api/v1beta1"
)
const (
@@ -118,6 +120,9 @@ const (
DefaultKubeAPIArgAdmissionControlConfigFileValue = "/etc/kubernetes/admission.yaml"
EventRateLimitPluginName = "EventRateLimit"
PodSecurityPluginName = "PodSecurity"
PodSecurityPrivileged = "privileged"
PodSecurityRestricted = "restricted"
KubeAPIArgAuditLogPath = "audit-log-path"
KubeAPIArgAuditLogMaxAge = "audit-log-maxage"
@@ -382,6 +387,9 @@ func (c *Cluster) setClusterServicesDefaults() {
c.Services.KubeAPI.EventRateLimit.Configuration == nil {
c.Services.KubeAPI.EventRateLimit.Configuration = newDefaultEventRateLimitConfig()
}
if len(c.Services.KubeAPI.PodSecurityConfiguration) == 0 {
c.Services.KubeAPI.PodSecurityConfiguration = PodSecurityPrivileged
}
}
enableKubeAPIAuditLog, err := checkVersionNeedsKubeAPIAuditLog(c.Version)
@@ -500,6 +508,120 @@ func newDefaultAdmissionConfiguration() (*apiserverv1.AdmissionConfiguration, er
return admissionConfiguration, nil
}
func newDefaultPodSecurityPluginConfigurationRestricted(Version string) (apiserverv1.AdmissionPluginConfiguration, error) {
plugin := apiserverv1.AdmissionPluginConfiguration{
Name: PodSecurityPluginName,
Configuration: &runtime.Unknown{
ContentType: "application/json",
},
}
parsedVersion, err := getClusterVersion(Version)
if err != nil {
return plugin, err
}
var cBytes []byte
if parsedRangeAtLeast125(parsedVersion) {
configuration := admissionapiv1.PodSecurityConfiguration{
TypeMeta: v1.TypeMeta{
Kind: "PodSecurityConfiguration",
APIVersion: admissionapiv1.SchemeGroupVersion.String(),
},
Defaults: admissionapiv1.PodSecurityDefaults{
Enforce: "restricted",
EnforceVersion: "latest",
Audit: "restricted",
AuditVersion: "latest",
Warn: "restricted",
WarnVersion: "latest",
},
Exemptions: admissionapiv1.PodSecurityExemptions{
Usernames: nil,
Namespaces: []string{"ingress-nginx", "kube-system"},
RuntimeClasses: nil,
},
}
cBytes, err = json.Marshal(configuration)
if err != nil {
return plugin, fmt.Errorf("error marshalling podSecurity config: %v", err)
}
}
if parsedRange123(parsedVersion) || parsedRange124(parsedVersion) {
configuration := admissionapiv1beta1.PodSecurityConfiguration{
TypeMeta: v1.TypeMeta{
Kind: "PodSecurityConfiguration",
APIVersion: admissionapiv1beta1.SchemeGroupVersion.String(),
},
Defaults: admissionapiv1beta1.PodSecurityDefaults{
Enforce: "restricted",
EnforceVersion: "latest",
Audit: "restricted",
AuditVersion: "latest",
Warn: "restricted",
WarnVersion: "latest",
},
Exemptions: admissionapiv1beta1.PodSecurityExemptions{
Usernames: nil,
Namespaces: []string{"ingress-nginx", "kube-system"},
RuntimeClasses: nil,
},
}
cBytes, err = json.Marshal(configuration)
if err != nil {
return plugin, fmt.Errorf("error marshalling podSecurity config: %v", err)
}
}
plugin.Configuration.Raw = cBytes
return plugin, nil
}
func newDefaultPodSecurityPluginConfigurationPrivileged(Version string) (apiserverv1.AdmissionPluginConfiguration, error) {
plugin := apiserverv1.AdmissionPluginConfiguration{
Name: PodSecurityPluginName,
Configuration: &runtime.Unknown{
ContentType: "application/json",
},
}
parsedVersion, err := getClusterVersion(Version)
if err != nil {
return plugin, err
}
var cBytes []byte
if parsedRangeAtLeast125(parsedVersion) {
configuration := admissionapiv1.PodSecurityConfiguration{
TypeMeta: v1.TypeMeta{
Kind: "PodSecurityConfiguration",
APIVersion: admissionapiv1.SchemeGroupVersion.String(),
},
Defaults: admissionapiv1.PodSecurityDefaults{
Enforce: "privileged",
EnforceVersion: "latest",
},
}
cBytes, err = json.Marshal(configuration)
if err != nil {
return plugin, fmt.Errorf("error marshalling podSecurity config: %v", err)
}
}
if parsedRange123(parsedVersion) || parsedRange124(parsedVersion) {
configuration := admissionapiv1beta1.PodSecurityConfiguration{
TypeMeta: v1.TypeMeta{
Kind: "PodSecurityConfiguration",
APIVersion: admissionapiv1beta1.SchemeGroupVersion.String(),
},
Defaults: admissionapiv1beta1.PodSecurityDefaults{
Enforce: "privileged",
EnforceVersion: "latest",
},
}
cBytes, err = json.Marshal(configuration)
if err != nil {
return plugin, fmt.Errorf("error marshalling podSecurity config: %v", err)
}
}
plugin.Configuration.Raw = cBytes
return plugin, nil
}
func (c *Cluster) setClusterImageDefaults() error {
var privRegURL string

View File

@@ -148,67 +148,78 @@ func (c *Cluster) CalculateMaxUnavailable() (int, int, error) {
return maxUnavailableWorker, maxUnavailableControl, nil
}
// getConsolidatedAdmissionConfiguration returns a consolidated admission configuration;
// for individual plugin configuration, the one under KubeAPI.AdmissionConfiguration takes precedence over the one under KubeAPI.<PLUGIN-NAME>
func (c *Cluster) getConsolidatedAdmissionConfiguration() (*apiserverv1.AdmissionConfiguration, error) {
var err error
var admissionConfig *apiserverv1.AdmissionConfiguration
admissionConfig, err := newDefaultAdmissionConfiguration()
if err != nil {
logrus.Errorf("error getting default admission configuration: %v", err)
return nil, err
}
if c.Services.KubeAPI.AdmissionConfiguration != nil {
copy(admissionConfig.Plugins, c.Services.KubeAPI.AdmissionConfiguration.Plugins)
}
// EventRateLimit
ertConfig, err := c.getEventRateLimitPluginConfiguration()
if err != nil {
return nil, err
}
_ = setPluginConfiguration(admissionConfig, ertConfig)
if c.Services.KubeAPI.EventRateLimit == nil ||
!c.Services.KubeAPI.EventRateLimit.Enabled {
return c.Services.KubeAPI.AdmissionConfiguration, nil
// PodSecurity
psConfig, err := c.getPodSecurityAdmissionPluginConfiguration()
if err != nil {
return nil, err
}
_ = setPluginConfiguration(admissionConfig, psConfig)
return admissionConfig, nil
}
logrus.Debugf("EventRateLimit is enabled")
found := false
func (c *Cluster) getEventRateLimitPluginConfiguration() (apiserverv1.AdmissionPluginConfiguration, error) {
// the configuration under KubeAPI.AdmissionConfiguration takes precedence over the one under KubeAPI.EventRateLimit
if c.Services.KubeAPI.AdmissionConfiguration != nil {
plugins := c.Services.KubeAPI.AdmissionConfiguration.Plugins
for _, plugin := range plugins {
if plugin.Name == EventRateLimitPluginName {
found = true
break
logrus.Debug("using the EventRateLimit configuration under the admission configuration")
return plugin, nil
}
}
}
if found {
logrus.Debugf("EventRateLimit Plugin configuration found in admission config")
if c.Services.KubeAPI.EventRateLimit.Configuration != nil {
logrus.Warnf("conflicting EventRateLimit configuration found, using the one from Admission Configuration")
return c.Services.KubeAPI.AdmissionConfiguration, nil
if c.Services.KubeAPI.EventRateLimit != nil &&
c.Services.KubeAPI.EventRateLimit.Enabled &&
c.Services.KubeAPI.EventRateLimit.Configuration != nil {
logrus.Debug("using the user-specified EventRateLimit configuration")
return getEventRateLimitPluginFromConfig(c.Services.KubeAPI.EventRateLimit.Configuration)
}
logrus.Debug("using the default EventRateLimit configuration")
return newDefaultEventRateLimitPlugin()
}
logrus.Debugf("EventRateLimit Plugin configuration not found in admission config")
if c.Services.KubeAPI.AdmissionConfiguration == nil {
logrus.Debugf("no user specified admission configuration found")
admissionConfig, err = newDefaultAdmissionConfiguration()
if err != nil {
logrus.Errorf("error getting default admission configuration: %v", err)
return nil, err
func (c *Cluster) getPodSecurityAdmissionPluginConfiguration() (apiserverv1.AdmissionPluginConfiguration, error) {
// the configuration under KubeAPI.AdmissionConfiguration takes precedence over
// the one under KubeAPI.PodSecurityConfiguration
if c.Services.KubeAPI.AdmissionConfiguration != nil {
plugins := c.Services.KubeAPI.AdmissionConfiguration.Plugins
for _, plugin := range plugins {
logrus.Debug("using the PodSecurity configuration under the admission configuration")
if plugin.Name == PodSecurityPluginName {
return plugin, nil
}
} else {
admissionConfig, err = newDefaultAdmissionConfiguration()
if err != nil {
logrus.Errorf("error getting default admission configuration: %v", err)
return nil, err
}
copy(admissionConfig.Plugins, c.Services.KubeAPI.AdmissionConfiguration.Plugins)
}
if c.Services.KubeAPI.EventRateLimit.Configuration != nil {
logrus.Debugf("user specified EventRateLimit configuration found")
p, err := getEventRateLimitPluginFromConfig(c.Services.KubeAPI.EventRateLimit.Configuration)
if err != nil {
logrus.Errorf("error getting eventratelimit plugin from config: %v", err)
level := c.Services.KubeAPI.PodSecurityConfiguration
logrus.Debugf("using the PodSecurity configuration [%s]", level)
switch level {
case PodSecurityPrivileged:
return newDefaultPodSecurityPluginConfigurationPrivileged(c.Version)
case PodSecurityRestricted:
return newDefaultPodSecurityPluginConfigurationRestricted(c.Version)
default:
logrus.Debugf("invalid PodSecurity configuration [%s], using the default [privileged] configuration", level)
return newDefaultPodSecurityPluginConfigurationPrivileged(c.Version)
}
admissionConfig.Plugins = append(admissionConfig.Plugins, p)
} else {
logrus.Debugf("using default EventRateLimit configuration")
p, err := newDefaultEventRateLimitPlugin()
if err != nil {
logrus.Errorf("error getting default eventratelimit plugin: %v", err)
}
admissionConfig.Plugins = append(admissionConfig.Plugins, p)
}
return admissionConfig, nil
}
func (c *Cluster) SetUpHosts(ctx context.Context, flags ExternalFlags) error {
@@ -270,7 +281,6 @@ func (c *Cluster) SetUpHosts(ctx context.Context, flags ExternalFlags) error {
}
if _, ok := c.Services.KubeAPI.ExtraArgs[KubeAPIArgAdmissionControlConfigFile]; !ok {
if c.Services.KubeAPI.EventRateLimit != nil && c.Services.KubeAPI.EventRateLimit.Enabled {
controlPlaneHosts := hosts.GetUniqueHostList(nil, c.ControlPlaneHosts, nil)
ac, err := c.getConsolidatedAdmissionConfiguration()
if err != nil {
@@ -285,7 +295,6 @@ func (c *Cluster) SetUpHosts(ctx context.Context, flags ExternalFlags) error {
}
log.Infof(ctx, "[%s] Successfully deployed admission control config to Cluster control nodes", DefaultKubeAPIArgAdmissionControlConfigFileValue)
}
}
if _, ok := c.Services.KubeAPI.ExtraArgs[KubeAPIArgAuditPolicyFile]; !ok {
if c.Services.KubeAPI.AuditLog != nil && c.Services.KubeAPI.AuditLog.Enabled {
@@ -332,3 +341,18 @@ func removeFromRKENodes(nodeToRemove v3.RKEConfigNode, nodeList []v3.RKEConfigNo
}
return l
}
// setPluginConfiguration either adds the plugin configuration or replaces the existing one in the admission configuration
func setPluginConfiguration(admissionConfig *apiserverv1.AdmissionConfiguration, pluginConfig apiserverv1.AdmissionPluginConfiguration) error {
if admissionConfig == nil {
return fmt.Errorf("admission configuarion does not exist")
}
for i, plugin := range admissionConfig.Plugins {
if plugin.Name == pluginConfig.Name {
admissionConfig.Plugins[i] = pluginConfig
return nil
}
}
admissionConfig.Plugins = append(admissionConfig.Plugins, pluginConfig)
return nil
}

View File

@@ -66,8 +66,11 @@ const (
var (
admissionControlOptionNames = []string{"enable-admission-plugins", "admission-control"}
parsedRangeAtLeast123 = semver.MustParseRange(">= 1.23.0-rancher0")
parsedRangeAtLeast124 = semver.MustParseRange(">= 1.24.0-rancher0")
parsedRangeAtLeast125 = semver.MustParseRange(">= 1.25.0-rancher0")
parsedRange123 = semver.MustParseRange(">=1.23.0-rancher0 <=1.23.99-rancher-0")
parsedRange124 = semver.MustParseRange(">=1.24.0-rancher0 <=1.24.99-rancher-0")
)
func GetServiceOptionData(data map[string]interface{}) map[string]*v3.KubernetesServicesOptions {
@@ -174,6 +177,7 @@ func (c *Cluster) BuildKubeAPIProcess(host *hosts.Host, serviceOptions v3.Kubern
Command := c.getRKEToolsEntryPoint(host.OS(), "kube-apiserver")
CommandArgs := map[string]string{
"admission-control-config-file": DefaultKubeAPIArgAdmissionControlConfigFileValue,
"client-ca-file": pki.GetCertPath(pki.CACertName),
"cloud-provider": c.CloudProvider.Name,
"etcd-cafile": etcdCAClientCert,
@@ -256,7 +260,6 @@ func (c *Cluster) BuildKubeAPIProcess(host *hosts.Host, serviceOptions v3.Kubern
}
if c.Services.KubeAPI.EventRateLimit != nil && c.Services.KubeAPI.EventRateLimit.Enabled {
CommandArgs[KubeAPIArgAdmissionControlConfigFile] = DefaultKubeAPIArgAdmissionControlConfigFileValue
CommandArgs[admissionControlOptionName] = CommandArgs[admissionControlOptionName] + ",EventRateLimit"
}

View File

@@ -3,6 +3,7 @@ package cluster
import (
"context"
"fmt"
"reflect"
"time"
"github.com/rancher/rke/docker"
@@ -488,3 +489,18 @@ func getTaintKey(taint v3.RKETaint) string {
func getTaintValue(taint v3.RKETaint) string {
return fmt.Sprintf("%s=%s:%s", taint.Key, taint.Value, taint.Effect)
}
// RestartKubeAPIServerWhenConfigChanges restarts the kube-apiserver container on the control plan nodes
// when changes are detected on the to-be-applied kube-api configuration. This is needed to handle the case
// where changes happen on the generated admission-control-config-file but not on the kube-apiserver container
func RestartKubeAPIServerWhenConfigChanges(ctx context.Context, kubeCluster, currentCluster *Cluster) error {
if currentCluster == nil {
return nil
}
if !reflect.DeepEqual(currentCluster.Services.KubeAPI, kubeCluster.Services.KubeAPI) {
for _, host := range kubeCluster.ControlPlaneHosts {
return services.RestartKubeAPI(ctx, host)
}
}
return nil
}

View File

@@ -210,7 +210,7 @@ func (s *FullState) WriteStateFile(ctx context.Context, statePath string) error
return fmt.Errorf("Failed to Marshal state object: %v", err)
}
logrus.Tracef("Writing state file: %s", stateFile)
if err := ioutil.WriteFile(statePath, stateFile, 0600); err != nil {
if err := os.WriteFile(statePath, stateFile, 0600); err != nil {
return fmt.Errorf("Failed to write state file: %v", err)
}
log.Infof(ctx, "Successfully Deployed state file at [%s]", statePath)

View File

@@ -55,7 +55,11 @@ func (c *Cluster) ValidateCluster(ctx context.Context) error {
}
// validate enabling Pod Security Policy
if err := validatePSP(c); err != nil {
if err := validatePodSecurityPolicy(c); err != nil {
return err
}
// validate enabling Pod Security
if err := validatePodSecurity(c); err != nil {
return err
}
@@ -655,12 +659,13 @@ func validateCRIDockerdOption(c *Cluster) error {
return nil
}
func validatePSP(c *Cluster) error {
func validatePodSecurityPolicy(c *Cluster) error {
parsedVersion, err := getClusterVersion(c.Version)
if err != nil {
logrus.Warnf("Failed to parse semver range for validating Pod Security Policy")
return err
}
logrus.Debugf("Checking PodSecurityPolicy for cluster version [%s]", c.Version)
if c.Services.KubeAPI.PodSecurityPolicy {
if c.Authorization.Mode != services.RBACAuthorizationMode {
return errors.New("PodSecurityPolicy can't be enabled with RBAC support disabled")
@@ -672,6 +677,29 @@ func validatePSP(c *Cluster) error {
return nil
}
func validatePodSecurity(c *Cluster) error {
parsedVersion, err := getClusterVersion(c.Version)
if err != nil {
logrus.Warnf("Failed to parse semver range for validating Pod Security")
return err
}
logrus.Debugf("Checking PodSecurity for cluster version [%s]", c.Version)
level := c.Services.KubeAPI.PodSecurityConfiguration
if len(level) != 0 {
if c.Authorization.Mode != services.RBACAuthorizationMode {
return errors.New("PodSecurity can't be enabled with RBAC support disabled")
}
if !parsedRangeAtLeast123(parsedVersion) {
return errors.New("cluster version must be at least v1.23 to use PodSecurity in RKE")
}
if level != PodSecurityPrivileged && level != PodSecurityRestricted {
return fmt.Errorf("invalid pod_security_configuration [%s]. Supported values: [%s, %s]",
level, PodSecurityPrivileged, PodSecurityRestricted)
}
}
return nil
}
func getClusterVersion(version string) (semver.Version, error) {
var parsedVersion semver.Version
if len(version) <= 1 || !strings.HasPrefix(version, "v") {

View File

@@ -200,6 +200,10 @@ func ClusterUp(ctx context.Context, dialersOptions hosts.DialersOptions, flags c
return APIURL, caCrt, clientCert, clientKey, nil, err
}
if err := cluster.RestartKubeAPIServerWhenConfigChanges(ctx, kubeCluster, currentCluster); err != nil {
return APIURL, caCrt, clientCert, clientKey, nil, err
}
if err := kubeCluster.PrePullK8sImages(ctx); err != nil {
return APIURL, caCrt, clientCert, clientKey, nil, err
}

1
go.mod
View File

@@ -40,6 +40,7 @@ require (
k8s.io/gengo v0.0.0-20211129171323-c02415ce4185
k8s.io/kubectl v0.25.3
k8s.io/kubernetes v1.13.0
k8s.io/pod-security-admission v0.25.3
sigs.k8s.io/yaml v1.2.0
)

2
go.sum
View File

@@ -1806,6 +1806,8 @@ k8s.io/kubectl v0.25.3/go.mod h1:glU7PiVj/R6Ud4A9FJdTcJjyzOtCJyc0eO7Mrbh3jlI=
k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/metrics v0.0.0-20191214191643-6b1944c9f765/go.mod h1:5V7rewilItwK0cz4nomU0b3XCcees2Ka5EBYWS1HBeM=
k8s.io/pod-security-admission v0.25.3 h1:2HnXWKUIDSez2sWtvxeGgGVUFvYnJJHutL4AI1MIuwk=
k8s.io/pod-security-admission v0.25.3/go.mod h1:xSaLkcMPD6cGKrZ//ZUrCNs0BewZzQdOEcC9LuXBGR4=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=

View File

@@ -295,6 +295,8 @@ type KubeAPIService struct {
ServiceNodePortRange string `yaml:"service_node_port_range" json:"serviceNodePortRange,omitempty" norman:"default=30000-32767"`
// Enabled/Disable PodSecurityPolicy
PodSecurityPolicy bool `yaml:"pod_security_policy" json:"podSecurityPolicy,omitempty"`
// setting the default configuration for PodSecurityAdmission
PodSecurityConfiguration string `yaml:"pod_security_configuration" json:"podSecurityConfiguration,omitempty" norman:"default=privileged"`
// Enable/Disable AlwaysPullImages admissions plugin
AlwaysPullImages bool `yaml:"always_pull_images" json:"alwaysPullImages,omitempty"`
// Secrets encryption provider config
@@ -920,14 +922,14 @@ type GlobalAwsOpts struct {
// Security group for each ELB this security group will be used instead.
ElbSecurityGroup string `json:"elb-security-group" yaml:"elb-security-group" ini:"ElbSecurityGroup,omitempty"`
// During the instantiation of an new AWS cloud provider, the detected region
// During the instantiation of a new AWS cloud provider, the detected region
// is validated against a known set of regions.
//
// In a non-standard, AWS like environment (e.g. Eucalyptus), this check may
// be undesirable. Setting this to true will disable the check and provide
// a warning that the check was skipped. Please note that this is an
// experimental feature and work-in-progress for the moment. If you find
// yourself in an non-AWS cloud and open an issue, please indicate that in the
// yourself in a non-AWS cloud and open an issue, please indicate that in the
// issue body.
DisableStrictZoneCheck bool `json:"disable-strict-zone-check" yaml:"disable-strict-zone-check" ini:"DisableStrictZoneCheck,omitempty"`
}