diff --git a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go index 54195b2b2b6..e6fae013518 100644 --- a/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go +++ b/cmd/kubeadm/app/apis/kubeadm/fuzzer/fuzzer.go @@ -110,6 +110,11 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { ConfigSyncPeriod: metav1.Duration{Duration: 1}, }, } + obj.AuditPolicyConfiguration = kubeadm.AuditPolicyConfiguration{ + Path: "foo", + LogDir: "/foo", + LogMaxAge: utilpointer.Int32Ptr(0), + } }, func(obj *kubeadm.NodeConfiguration, c fuzz.Continue) { c.FuzzNoCustom(obj) diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go index b0c26279348..100bb3170db 100644 --- a/cmd/kubeadm/app/apis/kubeadm/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/types.go @@ -108,6 +108,9 @@ type MasterConfiguration struct { // used for all control plane components. UnifiedControlPlaneImage string + // AuditPolicyConfiguration defines the options for the api server audit system. + AuditPolicyConfiguration AuditPolicyConfiguration + // FeatureGates enabled by the user. FeatureGates map[string]bool } @@ -263,3 +266,14 @@ type HostPathMount struct { type KubeProxy struct { Config *kubeproxyconfigv1alpha1.KubeProxyConfiguration } + +// AuditPolicyConfiguration holds the options for configuring the api server audit policy. +type AuditPolicyConfiguration struct { + // Path is the local path to an audit policy. + Path string + // LogDir is the local path to the directory where logs should be stored. + LogDir string + // LogMaxAge is the number of days logs will be stored for. 0 indicates forever. + LogMaxAge *int32 + //TODO(chuckha) add other options for audit policy. +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go index 6920ab07615..3a5fb047f50 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/defaults.go @@ -68,6 +68,12 @@ const ( KubeproxyKubeConfigFileName = "/var/lib/kube-proxy/kubeconfig.conf" ) +var ( + // DefaultAuditPolicyLogMaxAge is defined as a var so its address can be taken + // It is the number of days to store audit logs + DefaultAuditPolicyLogMaxAge = int32(2) +) + func addDefaultingFuncs(scheme *runtime.Scheme) error { return RegisterDefaults(scheme) } @@ -117,6 +123,7 @@ func SetDefaults_MasterConfiguration(obj *MasterConfiguration) { SetDefaults_KubeletConfiguration(obj) } SetDefaults_ProxyConfiguration(obj) + SetDefaults_AuditPolicyConfiguration(obj) } // SetDefaults_ProxyConfiguration assigns default values for the Proxy @@ -207,3 +214,13 @@ func SetDefaults_KubeletConfiguration(obj *MasterConfiguration) { scheme.Default(obj.KubeletConfiguration.BaseConfig) } } + +// SetDefaults_AuditPolicyConfiguration sets default values for the AuditPolicyConfiguration +func SetDefaults_AuditPolicyConfiguration(obj *MasterConfiguration) { + if obj.AuditPolicyConfiguration.LogDir == "" { + obj.AuditPolicyConfiguration.LogDir = constants.StaticPodAuditPolicyLogDir + } + if obj.AuditPolicyConfiguration.LogMaxAge == nil { + obj.AuditPolicyConfiguration.LogMaxAge = &DefaultAuditPolicyLogMaxAge + } +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go index 22a86c14dda..efb876cd1e5 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/types.go @@ -100,6 +100,9 @@ type MasterConfiguration struct { // be used for all control plane components. UnifiedControlPlaneImage string `json:"unifiedControlPlaneImage"` + // AuditPolicyConfiguration defines the options for the api server audit system + AuditPolicyConfiguration AuditPolicyConfiguration `json:"auditPolicy"` + // FeatureGates enabled by the user. FeatureGates map[string]bool `json:"featureGates,omitempty"` } @@ -243,3 +246,14 @@ type HostPathMount struct { type KubeProxy struct { Config *kubeproxyconfigv1alpha1.KubeProxyConfiguration `json:"config,omitempty"` } + +// AuditPolicyConfiguration holds the options for configuring the api server audit policy. +type AuditPolicyConfiguration struct { + // Path is the local path to an audit policy. + Path string `json:"path"` + // LogDir is the local path to the directory where logs should be stored. + LogDir string `json:"logDir"` + // LogMaxAge is the number of days logs will be stored for. 0 indicates forever. + LogMaxAge *int32 `json:"logMaxAge,omitempty"` + //TODO(chuckha) add other options for audit policy. +} diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go index c76bcd5143c..6eada649849 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.conversion.go @@ -42,6 +42,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { return scheme.AddGeneratedConversionFuncs( Convert_v1alpha1_API_To_kubeadm_API, Convert_kubeadm_API_To_v1alpha1_API, + Convert_v1alpha1_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration, + Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha1_AuditPolicyConfiguration, Convert_v1alpha1_Etcd_To_kubeadm_Etcd, Convert_kubeadm_Etcd_To_v1alpha1_Etcd, Convert_v1alpha1_HostPathMount_To_kubeadm_HostPathMount, @@ -85,6 +87,30 @@ func Convert_kubeadm_API_To_v1alpha1_API(in *kubeadm.API, out *API, s conversion return autoConvert_kubeadm_API_To_v1alpha1_API(in, out, s) } +func autoConvert_v1alpha1_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration(in *AuditPolicyConfiguration, out *kubeadm.AuditPolicyConfiguration, s conversion.Scope) error { + out.Path = in.Path + out.LogDir = in.LogDir + out.LogMaxAge = (*int32)(unsafe.Pointer(in.LogMaxAge)) + return nil +} + +// Convert_v1alpha1_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration(in *AuditPolicyConfiguration, out *kubeadm.AuditPolicyConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration(in, out, s) +} + +func autoConvert_kubeadm_AuditPolicyConfiguration_To_v1alpha1_AuditPolicyConfiguration(in *kubeadm.AuditPolicyConfiguration, out *AuditPolicyConfiguration, s conversion.Scope) error { + out.Path = in.Path + out.LogDir = in.LogDir + out.LogMaxAge = (*int32)(unsafe.Pointer(in.LogMaxAge)) + return nil +} + +// Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha1_AuditPolicyConfiguration is an autogenerated conversion function. +func Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha1_AuditPolicyConfiguration(in *kubeadm.AuditPolicyConfiguration, out *AuditPolicyConfiguration, s conversion.Scope) error { + return autoConvert_kubeadm_AuditPolicyConfiguration_To_v1alpha1_AuditPolicyConfiguration(in, out, s) +} + func autoConvert_v1alpha1_Etcd_To_kubeadm_Etcd(in *Etcd, out *kubeadm.Etcd, s conversion.Scope) error { out.Endpoints = *(*[]string)(unsafe.Pointer(&in.Endpoints)) out.CAFile = in.CAFile @@ -217,6 +243,9 @@ func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in out.ImageRepository = in.ImageRepository out.ImagePullPolicy = core_v1.PullPolicy(in.ImagePullPolicy) out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage + if err := Convert_v1alpha1_AuditPolicyConfiguration_To_kubeadm_AuditPolicyConfiguration(&in.AuditPolicyConfiguration, &out.AuditPolicyConfiguration, s); err != nil { + return err + } out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) return nil } @@ -261,6 +290,9 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in out.ImageRepository = in.ImageRepository // INFO: in.CIImageRepository opted out of conversion generation out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage + if err := Convert_kubeadm_AuditPolicyConfiguration_To_v1alpha1_AuditPolicyConfiguration(&in.AuditPolicyConfiguration, &out.AuditPolicyConfiguration, s); err != nil { + return err + } out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) return nil } diff --git a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go index 10188014de8..9964e7a2117 100644 --- a/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/v1alpha1/zz_generated.deepcopy.go @@ -43,6 +43,31 @@ func (in *API) DeepCopy() *API { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuditPolicyConfiguration) DeepCopyInto(out *AuditPolicyConfiguration) { + *out = *in + if in.LogMaxAge != nil { + in, out := &in.LogMaxAge, &out.LogMaxAge + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuditPolicyConfiguration. +func (in *AuditPolicyConfiguration) DeepCopy() *AuditPolicyConfiguration { + if in == nil { + return nil + } + out := new(AuditPolicyConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Etcd) DeepCopyInto(out *Etcd) { *out = *in @@ -210,6 +235,7 @@ func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { *out = make([]string, len(*in)) copy(*out, *in) } + in.AuditPolicyConfiguration.DeepCopyInto(&out.AuditPolicyConfiguration) if in.FeatureGates != nil { in, out := &in.FeatureGates, &out.FeatureGates *out = make(map[string]bool, len(*in)) diff --git a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go index c66ee3383bd..8f5f85f1788 100644 --- a/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go +++ b/cmd/kubeadm/app/apis/kubeadm/zz_generated.deepcopy.go @@ -43,6 +43,31 @@ func (in *API) DeepCopy() *API { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuditPolicyConfiguration) DeepCopyInto(out *AuditPolicyConfiguration) { + *out = *in + if in.LogMaxAge != nil { + in, out := &in.LogMaxAge, &out.LogMaxAge + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuditPolicyConfiguration. +func (in *AuditPolicyConfiguration) DeepCopy() *AuditPolicyConfiguration { + if in == nil { + return nil + } + out := new(AuditPolicyConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Etcd) DeepCopyInto(out *Etcd) { *out = *in @@ -210,6 +235,7 @@ func (in *MasterConfiguration) DeepCopyInto(out *MasterConfiguration) { *out = make([]string, len(*in)) copy(*out, *in) } + in.AuditPolicyConfiguration.DeepCopyInto(&out.AuditPolicyConfiguration) if in.FeatureGates != nil { in, out := &in.FeatureGates, &out.FeatureGates *out = make(map[string]bool, len(*in)) diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 448a61ed0cf..0be6afd80f6 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -47,6 +47,7 @@ go_library( "//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", + "//cmd/kubeadm/app/util/audit:go_default_library", "//cmd/kubeadm/app/util/config:go_default_library", "//cmd/kubeadm/app/util/dryrun:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 70ff745c5ea..9d28b1206ba 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -57,6 +57,7 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" + auditutil "k8s.io/kubernetes/cmd/kubeadm/app/util/audit" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" @@ -322,6 +323,21 @@ func (i *Init) Run(out io.Writer) error { fmt.Println("[externalca] The file 'ca.key' was not found, yet all other certificates are present. Using external CA mode - certificates or kubeconfig will not be generated.") } + if features.Enabled(i.cfg.FeatureGates, features.Auditing) { + // Setup the AuditPolicy (either it was passed in and exists or it wasn't passed in and generate a default policy) + if i.cfg.AuditPolicyConfiguration.Path != "" { + // TODO(chuckha) ensure passed in audit policy is valid so users don't have to find the error in the api server log. + if _, err := os.Stat(i.cfg.AuditPolicyConfiguration.Path); err != nil { + return fmt.Errorf("error getting file info for audit policy file %q [%v]", i.cfg.AuditPolicyConfiguration.Path, err) + } + } else { + i.cfg.AuditPolicyConfiguration.Path = filepath.Join(kubeConfigDir, kubeadmconstants.AuditPolicyDir, kubeadmconstants.AuditPolicyFile) + if err := auditutil.CreateDefaultAuditLogPolicy(i.cfg.AuditPolicyConfiguration.Path); err != nil { + return fmt.Errorf("error creating default audit policy %q [%v]", i.cfg.AuditPolicyConfiguration.Path, err) + } + } + } + // Temporarily set cfg.CertificatesDir to the "real value" when writing controlplane manifests // This is needed for writing the right kind of manifests i.cfg.CertificatesDir = realCertsDir diff --git a/cmd/kubeadm/app/cmd/upgrade/common_test.go b/cmd/kubeadm/app/cmd/upgrade/common_test.go index 61b2ee37e79..83af9150554 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common_test.go +++ b/cmd/kubeadm/app/cmd/upgrade/common_test.go @@ -41,6 +41,9 @@ func TestPrintConfiguration(t *testing.T) { api: advertiseAddress: "" bindPort: 0 + auditPolicy: + logDir: "" + path: "" certificatesDir: "" cloudProvider: "" etcd: @@ -75,6 +78,9 @@ func TestPrintConfiguration(t *testing.T) { api: advertiseAddress: "" bindPort: 0 + auditPolicy: + logDir: "" + path: "" certificatesDir: "" cloudProvider: "" etcd: @@ -114,6 +120,9 @@ func TestPrintConfiguration(t *testing.T) { api: advertiseAddress: "" bindPort: 0 + auditPolicy: + logDir: "" + path: "" certificatesDir: "" cloudProvider: "" etcd: diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index 46f76fc2724..d6e10000205 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -203,6 +203,19 @@ const ( // CRICtlPackage defines the go package that installs crictl CRICtlPackage = "github.com/kubernetes-incubator/cri-tools/cmd/crictl" + + // KubeAuditPolicyVolumeName is the name of the volume that will contain the audit policy + KubeAuditPolicyVolumeName = "audit" + // AuditPolicyDir is the directory that will contain the audit policy + AuditPolicyDir = "audit" + // AuditPolicyFile is the name of the audit policy file itself + AuditPolicyFile = "audit.yaml" + // AuditPolicyLogFile is the name of the file audit logs get written to + AuditPolicyLogFile = "audit.log" + // KubeAuditPolicyLogVolumeName is the name of the volume that will contain the audit logs + KubeAuditPolicyLogVolumeName = "audit-log" + // StaticPodAuditPolicyLogDir is the name of the directory in the static pod that will have the audit logs + StaticPodAuditPolicyLogDir = "/var/log/kubernetes/audit" ) var ( @@ -311,3 +324,8 @@ func GetDNSIP(svcSubnet string) (net.IP, error) { return dnsIP, nil } + +// GetStaticPodAuditPolicyFile returns the path to the audit policy file within a static pod +func GetStaticPodAuditPolicyFile() string { + return filepath.Join(KubernetesDir, AuditPolicyDir, AuditPolicyFile) +} diff --git a/cmd/kubeadm/app/features/features.go b/cmd/kubeadm/app/features/features.go index 57627cb367b..9dae9c659f7 100644 --- a/cmd/kubeadm/app/features/features.go +++ b/cmd/kubeadm/app/features/features.go @@ -41,6 +41,9 @@ const ( // DynamicKubeletConfig is alpha in v1.9 DynamicKubeletConfig = "DynamicKubeletConfig" + + // Auditing is beta in 1.8 + Auditing = "Auditing" ) var v190 = version.MustParseSemantic("v1.9.0-alpha.1") @@ -53,6 +56,7 @@ var InitFeatureGates = FeatureList{ HighAvailability: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190, HiddenInHelpText: true}, CoreDNS: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, DynamicKubeletConfig: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, + Auditing: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}}, } // Feature represents a feature being gated diff --git a/cmd/kubeadm/app/phases/controlplane/BUILD b/cmd/kubeadm/app/phases/controlplane/BUILD index c706fc98714..a11eeb85ae1 100644 --- a/cmd/kubeadm/app/phases/controlplane/BUILD +++ b/cmd/kubeadm/app/phases/controlplane/BUILD @@ -21,6 +21,7 @@ go_test( "//cmd/kubeadm/app/phases/certs:go_default_library", "//cmd/kubeadm/test:go_default_library", "//pkg/master/reconcilers:go_default_library", + "//pkg/util/pointer:go_default_library", "//pkg/util/version:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", ], diff --git a/cmd/kubeadm/app/phases/controlplane/manifests.go b/cmd/kubeadm/app/phases/controlplane/manifests.go index dea08e79d55..8dbe1db716c 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests.go @@ -224,6 +224,16 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *versio command = append(command, "--feature-gates=DynamicKubeletConfig=true") } + if features.Enabled(cfg.FeatureGates, features.Auditing) { + command = append(command, "--audit-policy-file="+kubeadmconstants.GetStaticPodAuditPolicyFile()) + command = append(command, "--audit-log-path="+filepath.Join(kubeadmconstants.StaticPodAuditPolicyLogDir, kubeadmconstants.AuditPolicyLogFile)) + if cfg.AuditPolicyConfiguration.LogMaxAge == nil { + command = append(command, fmt.Sprintf("--audit-log-maxage=%d", kubeadmapiext.DefaultAuditPolicyLogMaxAge)) + } else { + command = append(command, fmt.Sprintf("--audit-log-maxage=%d", *cfg.AuditPolicyConfiguration.LogMaxAge)) + } + } + return command } diff --git a/cmd/kubeadm/app/phases/controlplane/manifests_test.go b/cmd/kubeadm/app/phases/controlplane/manifests_test.go index 30aca48a008..e12d700f10e 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests_test.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests_test.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/pkg/util/version" testutil "k8s.io/kubernetes/cmd/kubeadm/test" + utilpointer "k8s.io/kubernetes/pkg/util/pointer" ) const ( @@ -338,6 +339,11 @@ func TestGetAPIServerCommand(t *testing.T) { Etcd: kubeadmapi.Etcd{CertFile: "fiz", KeyFile: "faz"}, CertificatesDir: testCertsDir, KubernetesVersion: "v1.9.3", + AuditPolicyConfiguration: kubeadmapi.AuditPolicyConfiguration{ + Path: "/foo/bar", + LogDir: "/foo/baz", + LogMaxAge: utilpointer.Int32Ptr(10), + }, // ignored without the feature gate }, expected: []string{ "kube-apiserver", @@ -446,9 +452,12 @@ func TestGetAPIServerCommand(t *testing.T) { cfg: &kubeadmapi.MasterConfiguration{ API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "2001:db8::1"}, Networking: kubeadmapi.Networking{ServiceSubnet: "bar"}, - FeatureGates: map[string]bool{features.HighAvailability: true}, + FeatureGates: map[string]bool{features.HighAvailability: true, features.Auditing: true}, CertificatesDir: testCertsDir, KubernetesVersion: "v1.9.0-beta.0", + AuditPolicyConfiguration: kubeadmapi.AuditPolicyConfiguration{ + LogMaxAge: utilpointer.Int32Ptr(0), + }, }, expected: []string{ "kube-apiserver", @@ -476,6 +485,9 @@ func TestGetAPIServerCommand(t *testing.T) { "--advertise-address=2001:db8::1", "--etcd-servers=http://127.0.0.1:2379", fmt.Sprintf("--endpoint-reconciler-type=%s", reconcilers.LeaseEndpointReconcilerType), + "--audit-policy-file=/etc/kubernetes/audit/audit.yaml", + "--audit-log-path=/var/log/kubernetes/audit/audit.log", + "--audit-log-maxage=0", }, }, { diff --git a/cmd/kubeadm/app/phases/controlplane/volumes.go b/cmd/kubeadm/app/phases/controlplane/volumes.go index a3b5ce7fefc..02191d6b8f8 100644 --- a/cmd/kubeadm/app/phases/controlplane/volumes.go +++ b/cmd/kubeadm/app/phases/controlplane/volumes.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod" ) @@ -47,6 +48,7 @@ var caCertsPkiVolumePath = "/etc/pki" func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.MasterConfiguration) controlPlaneHostPathMounts { hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate hostPathFileOrCreate := v1.HostPathFileOrCreate + hostPathFile := v1.HostPathFile mounts := newControlPlaneHostPathMounts() // HostPath volumes for the API Server @@ -55,7 +57,12 @@ func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.MasterConfiguration) c mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeCertificatesVolumeName, cfg.CertificatesDir, cfg.CertificatesDir, true, &hostPathDirectoryOrCreate) // Read-only mount for the ca certs (/etc/ssl/certs) directory mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, caCertsVolumeName, caCertsVolumePath, caCertsVolumePath, true, &hostPathDirectoryOrCreate) - + if features.Enabled(cfg.FeatureGates, features.Auditing) { + // Read-only mount for the audit policy file. + mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeAuditPolicyVolumeName, cfg.AuditPolicyConfiguration.Path, kubeadmconstants.GetStaticPodAuditPolicyFile(), true, &hostPathFile) + // Write mount for the audit logs. + mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeAuditPolicyLogVolumeName, cfg.AuditPolicyConfiguration.LogDir, kubeadmconstants.StaticPodAuditPolicyLogDir, false, &hostPathDirectoryOrCreate) + } // If external etcd is specified, mount the directories needed for accessing the CA/serving certs and the private key if len(cfg.Etcd.Endpoints) != 0 { etcdVols, etcdVolMounts := getEtcdCertVolumes(cfg.Etcd, cfg.CertificatesDir) diff --git a/cmd/kubeadm/app/phases/controlplane/volumes_test.go b/cmd/kubeadm/app/phases/controlplane/volumes_test.go index a5fde3cf035..febd2c4291d 100644 --- a/cmd/kubeadm/app/phases/controlplane/volumes_test.go +++ b/cmd/kubeadm/app/phases/controlplane/volumes_test.go @@ -26,6 +26,7 @@ import ( "k8s.io/api/core/v1" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/features" ) func TestGetEtcdCertVolumes(t *testing.T) { @@ -258,6 +259,7 @@ func TestGetEtcdCertVolumes(t *testing.T) { func TestGetHostPathVolumesForTheControlPlane(t *testing.T) { hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate hostPathFileOrCreate := v1.HostPathFileOrCreate + hostPathFile := v1.HostPathFile volMap := make(map[string]map[string]v1.Volume) volMap[kubeadmconstants.KubeAPIServer] = map[string]v1.Volume{} volMap[kubeadmconstants.KubeAPIServer]["k8s-certs"] = v1.Volume{ @@ -278,6 +280,24 @@ func TestGetHostPathVolumesForTheControlPlane(t *testing.T) { }, }, } + volMap[kubeadmconstants.KubeAPIServer]["audit"] = v1.Volume{ + Name: "audit", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/foo/bar/baz.yaml", + Type: &hostPathFile, + }, + }, + } + volMap[kubeadmconstants.KubeAPIServer]["audit-log"] = v1.Volume{ + Name: "audit-log", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{ + Path: "/bar/foo", + Type: &hostPathDirectoryOrCreate, + }, + }, + } volMap[kubeadmconstants.KubeControllerManager] = map[string]v1.Volume{} volMap[kubeadmconstants.KubeControllerManager]["k8s-certs"] = v1.Volume{ Name: "k8s-certs", @@ -328,6 +348,16 @@ func TestGetHostPathVolumesForTheControlPlane(t *testing.T) { MountPath: "/etc/ssl/certs", ReadOnly: true, } + volMountMap[kubeadmconstants.KubeAPIServer]["audit"] = v1.VolumeMount{ + Name: "audit", + MountPath: "/etc/kubernetes/audit/audit.yaml", + ReadOnly: true, + } + volMountMap[kubeadmconstants.KubeAPIServer]["audit-log"] = v1.VolumeMount{ + Name: "audit-log", + MountPath: "/var/log/kubernetes/audit", + ReadOnly: false, + } volMountMap[kubeadmconstants.KubeControllerManager] = map[string]v1.VolumeMount{} volMountMap[kubeadmconstants.KubeControllerManager]["k8s-certs"] = v1.VolumeMount{ Name: "k8s-certs", @@ -481,6 +511,11 @@ func TestGetHostPathVolumesForTheControlPlane(t *testing.T) { cfg: &kubeadmapi.MasterConfiguration{ CertificatesDir: testCertsDir, Etcd: kubeadmapi.Etcd{}, + FeatureGates: map[string]bool{features.Auditing: true}, + AuditPolicyConfiguration: kubeadmapi.AuditPolicyConfiguration{ + Path: "/foo/bar/baz.yaml", + LogDir: "/bar/foo", + }, }, vol: volMap, volMount: volMountMap, diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index d67803c3d0f..a98eef896ce 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -61,6 +61,7 @@ filegroup( srcs = [ ":package-srcs", "//cmd/kubeadm/app/util/apiclient:all-srcs", + "//cmd/kubeadm/app/util/audit:all-srcs", "//cmd/kubeadm/app/util/config:all-srcs", "//cmd/kubeadm/app/util/dryrun:all-srcs", "//cmd/kubeadm/app/util/kubeconfig:all-srcs", diff --git a/cmd/kubeadm/app/util/audit/BUILD b/cmd/kubeadm/app/util/audit/BUILD new file mode 100644 index 00000000000..5e38d0caf47 --- /dev/null +++ b/cmd/kubeadm/app/util/audit/BUILD @@ -0,0 +1,40 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["utils.go"], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/audit", + visibility = ["//visibility:public"], + deps = [ + "//cmd/kubeadm/app/util:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["utils_test.go"], + embed = [":go_default_library"], + importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/audit", + deps = [ + "//pkg/kubectl/scheme:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/cmd/kubeadm/app/util/audit/utils.go b/cmd/kubeadm/app/util/audit/utils.go new file mode 100644 index 00000000000..688d8f0f614 --- /dev/null +++ b/cmd/kubeadm/app/util/audit/utils.go @@ -0,0 +1,67 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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. +*/ + +package audit + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/kubernetes/cmd/kubeadm/app/util" +) + +// CreateDefaultAuditLogPolicy writes the default audit log policy to disk. +func CreateDefaultAuditLogPolicy(policyFile string) error { + policy := auditv1beta1.Policy{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "audit.k8s.io/v1beta1", + Kind: "Policy", + }, + Rules: []auditv1beta1.PolicyRule{ + { + Level: auditv1beta1.LevelMetadata, + }, + }, + } + return writePolicyToDisk(policyFile, &policy) +} + +func writePolicyToDisk(policyFile string, policy *auditv1beta1.Policy) error { + // creates target folder if not already exists + if err := os.MkdirAll(filepath.Dir(policyFile), 0700); err != nil { + return fmt.Errorf("failed to create directory %q: %v", filepath.Dir(policyFile), err) + } + + // Registers auditv1beta1 with the runtime Scheme + auditv1beta1.AddToScheme(scheme.Scheme) + + // writes the policy to disk + serialized, err := util.MarshalToYaml(policy, auditv1beta1.SchemeGroupVersion) + if err != nil { + return fmt.Errorf("failed to marshal audit policy to YAML: %v", err) + } + + if err := ioutil.WriteFile(policyFile, serialized, 0600); err != nil { + return fmt.Errorf("failed to write audit policy to %v: %v", policyFile, err) + } + + return nil +} diff --git a/cmd/kubeadm/app/util/audit/utils_test.go b/cmd/kubeadm/app/util/audit/utils_test.go new file mode 100644 index 00000000000..113488a752f --- /dev/null +++ b/cmd/kubeadm/app/util/audit/utils_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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. +*/ + +package audit + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1" + "k8s.io/kubernetes/pkg/kubectl/scheme" +) + +func cleanup(t *testing.T, path string) { + err := os.RemoveAll(path) + if err != nil { + t.Fatalf("Failed to clean up %v: %v", path, err) + } +} + +func TestCreateDefaultAuditLogPolicy(t *testing.T) { + // make a tempdir + tempDir, err := ioutil.TempDir("/tmp", "audit-test") + if err != nil { + t.Fatalf("could not create a tempdir: %v", err) + } + defer cleanup(t, tempDir) + auditPolicyFile := filepath.Join(tempDir, "test.yaml") + if err = CreateDefaultAuditLogPolicy(auditPolicyFile); err != nil { + t.Fatalf("failed to create audit log policy: %v", err) + } + // turn the audit log back into a policy + policyBytes, err := ioutil.ReadFile(auditPolicyFile) + if err != nil { + t.Fatalf("failed to read %v: %v", auditPolicyFile, err) + } + policy := auditv1beta1.Policy{} + err = runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), policyBytes, &policy) + if err != nil { + t.Fatalf("failed to decode written policy: %v", err) + } + if policy.Kind != "Policy" { + t.Fatalf("did not decode policy properly") + } +}