mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
FEATURE/allow-to-customize-security-context (#526)
This commit is contained in:
parent
3d8b77e0d6
commit
40d738b771
12 changed files with 286 additions and 20 deletions
|
@ -1,6 +1,9 @@
|
|||
# Change Log
|
||||
|
||||
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
|
||||
- Add Customizable SecurityContext for ArangoDeployment pods
|
||||
|
||||
## [0.4.4](https://github.com/arangodb/kube-arangodb/tree/0.4.4) (2020-02-27)
|
||||
- Add new VolumeResize mode to be compatible with Azure flow
|
||||
- Allow to customize probe configuration options
|
||||
- Add new upgrade flag for ArangoDB 3.6.0<=
|
||||
|
|
|
@ -68,6 +68,71 @@ type ServerGroupSpec struct {
|
|||
VolumeResizeMode *PVCResizeMode `json:"pvcResizeMode,omitempty"`
|
||||
// Sidecars specifies a list of additional containers to be started
|
||||
Sidecars []v1.Container `json:"sidecars,omitempty"`
|
||||
// SecurityContext specifies security context for group
|
||||
SecurityContext *ServerGroupSpecSecurityContext `json:"securityContext,omitempty"`
|
||||
}
|
||||
|
||||
// ServerGroupSpecSecurityContext contains specification for pod security context
|
||||
type ServerGroupSpecSecurityContext struct {
|
||||
// DropAllCapabilities specifies if capabilities should be dropped for this pod containers
|
||||
//
|
||||
// Deprecated: This field is added for backward compatibility. Will be removed in 1.0.0.
|
||||
DropAllCapabilities *bool `json:"dropAllCapabilities,omitempty"`
|
||||
// AddCapabilities add new capabilities to containers
|
||||
AddCapabilities []v1.Capability `json:"addCapabilities,omitempty"`
|
||||
}
|
||||
|
||||
// GetDropAllCapabilities returns flag if capabilities should be dropped
|
||||
//
|
||||
// Deprecated: This function is added for backward compatibility. Will be removed in 1.0.0.
|
||||
func (s *ServerGroupSpecSecurityContext) GetDropAllCapabilities() bool {
|
||||
if s == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.DropAllCapabilities == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return *s.DropAllCapabilities
|
||||
}
|
||||
|
||||
// GetAddCapabilities add capabilities to pod context
|
||||
func (s *ServerGroupSpecSecurityContext) GetAddCapabilities() []v1.Capability {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.AddCapabilities == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.AddCapabilities
|
||||
}
|
||||
|
||||
// NewSecurityContext creates new security context
|
||||
func (s *ServerGroupSpecSecurityContext) NewSecurityContext() *v1.SecurityContext {
|
||||
r := &v1.SecurityContext{}
|
||||
|
||||
capabilities := &v1.Capabilities{}
|
||||
|
||||
if s.GetDropAllCapabilities() {
|
||||
capabilities.Drop = []v1.Capability{
|
||||
"ALL",
|
||||
}
|
||||
}
|
||||
|
||||
if caps := s.GetAddCapabilities(); caps != nil {
|
||||
capabilities.Add = []v1.Capability{}
|
||||
|
||||
for _, capability := range caps {
|
||||
capabilities.Add = append(capabilities.Add, capability)
|
||||
}
|
||||
}
|
||||
|
||||
r.Capabilities = capabilities
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ServerGroupProbesSpec contains specification for probes for pods of the server group
|
||||
|
|
31
pkg/apis/deployment/v1/zz_generated.deepcopy.go
generated
31
pkg/apis/deployment/v1/zz_generated.deepcopy.go
generated
|
@ -962,6 +962,11 @@ func (in *ServerGroupSpec) DeepCopyInto(out *ServerGroupSpec) {
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.SecurityContext != nil {
|
||||
in, out := &in.SecurityContext, &out.SecurityContext
|
||||
*out = new(ServerGroupSpecSecurityContext)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -975,6 +980,32 @@ func (in *ServerGroupSpec) DeepCopy() *ServerGroupSpec {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServerGroupSpecSecurityContext) DeepCopyInto(out *ServerGroupSpecSecurityContext) {
|
||||
*out = *in
|
||||
if in.DropAllCapabilities != nil {
|
||||
in, out := &in.DropAllCapabilities, &out.DropAllCapabilities
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.AddCapabilities != nil {
|
||||
in, out := &in.AddCapabilities, &out.AddCapabilities
|
||||
*out = make([]corev1.Capability, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerGroupSpecSecurityContext.
|
||||
func (in *ServerGroupSpecSecurityContext) DeepCopy() *ServerGroupSpecSecurityContext {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServerGroupSpecSecurityContext)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SyncAuthenticationSpec) DeepCopyInto(out *SyncAuthenticationSpec) {
|
||||
*out = *in
|
||||
|
|
|
@ -305,6 +305,10 @@ func (i *ImageUpdatePod) GetTolerations() []v1.Toleration {
|
|||
return tolerations
|
||||
}
|
||||
|
||||
func (a *ArangoDImageUpdateContainer) GetSecurityContext() *v1.SecurityContext {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImageUpdatePod) IsDeploymentMode() bool {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -207,6 +207,11 @@ func createPlan(log zerolog.Logger, apiObject k8sutil.APIObject,
|
|||
plan = createRotateServerStoragePlan(log, apiObject, spec, status, context.GetPvc, context.CreateEvent)
|
||||
}
|
||||
|
||||
// Adjust security
|
||||
if plan.IsEmpty() {
|
||||
plan = createRotateServerSecurityPlan(log, spec, status, pods)
|
||||
}
|
||||
|
||||
// Check for the need to rotate TLS CA certificate and all members
|
||||
if plan.IsEmpty() {
|
||||
plan = createRotateTLSCAPlan(log, apiObject, spec, status, context.GetTLSCA, context.CreateEvent)
|
||||
|
|
154
pkg/deployment/reconcile/plan_builder_security.go
Normal file
154
pkg/deployment/reconcile/plan_builder_security.go
Normal file
|
@ -0,0 +1,154 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2020 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Author Adam Janikowski
|
||||
//
|
||||
|
||||
package reconcile
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||
)
|
||||
|
||||
// createRotateServerStoragePlan creates plan to rotate a server and its volume because of a
|
||||
// different storage class or a difference in storage resource requirements.
|
||||
func createRotateServerSecurityPlan(log zerolog.Logger, spec api.DeploymentSpec, status api.DeploymentStatus,
|
||||
pods []core.Pod) api.Plan {
|
||||
var plan api.Plan
|
||||
status.Members.ForeachServerGroup(func(group api.ServerGroup, members api.MemberStatusList) error {
|
||||
for _, m := range members {
|
||||
if !plan.IsEmpty() {
|
||||
// Only 1 change at a time
|
||||
continue
|
||||
}
|
||||
|
||||
groupSpec := spec.GetServerGroupSpec(group)
|
||||
|
||||
pod, found := k8sutil.GetPodByName(pods, m.PodName)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
container, ok := getServerContainer(pod.Spec.Containers)
|
||||
if !ok {
|
||||
// We do not have server container in pod, which is not desired
|
||||
continue
|
||||
}
|
||||
|
||||
groupSC := groupSpec.SecurityContext.NewSecurityContext()
|
||||
containerSC := container.SecurityContext
|
||||
|
||||
if !compareSC(groupSC, containerSC) {
|
||||
log.Info().Str("member", m.ID).Str("group", group.AsRole()).Msg("Rotating security context")
|
||||
plan = append(plan,
|
||||
api.NewAction(api.ActionTypeRotateMember, group, m.ID),
|
||||
api.NewAction(api.ActionTypeWaitForMemberUp, group, m.ID),
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return plan
|
||||
}
|
||||
|
||||
func getServerContainer(containers []core.Container) (core.Container, bool) {
|
||||
for _, container := range containers {
|
||||
if container.Name == k8sutil.ServerContainerName {
|
||||
return container, true
|
||||
}
|
||||
}
|
||||
|
||||
return core.Container{}, false
|
||||
}
|
||||
|
||||
func compareSC(a,b *core.SecurityContext) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if ok := compareCapabilities(a.Capabilities, b.Capabilities); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func compareCapabilities(a,b *core.Capabilities) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if ok := compareCapabilityLists(a.Add, b.Add); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if ok := compareCapabilityLists(a.Drop, b.Drop); !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func compareCapabilityLists(a, b []core.Capability) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
checked := map[core.Capability]bool{}
|
||||
|
||||
for _, capability := range a {
|
||||
checked[capability] = false
|
||||
}
|
||||
|
||||
for _, capability := range b {
|
||||
if _, ok := checked[capability]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
checked[capability] = true
|
||||
}
|
||||
|
||||
for _, check := range checked {
|
||||
if !check {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -33,7 +33,7 @@ import (
|
|||
|
||||
// ArangodbExporterContainer creates metrics container
|
||||
func ArangodbExporterContainer(image string, args []string, livenessProbe *k8sutil.HTTPProbeConfig,
|
||||
resources v1.ResourceRequirements) v1.Container {
|
||||
resources v1.ResourceRequirements, securityContext *v1.SecurityContext) v1.Container {
|
||||
|
||||
c := v1.Container{
|
||||
Name: k8sutil.ExporterContainerName,
|
||||
|
@ -48,7 +48,7 @@ func ArangodbExporterContainer(image string, args []string, livenessProbe *k8sut
|
|||
},
|
||||
Resources: k8sutil.ExtractPodResourceRequirement(resources),
|
||||
ImagePullPolicy: v1.PullIfNotPresent,
|
||||
SecurityContext: k8sutil.SecurityContextWithoutCapabilities(),
|
||||
SecurityContext: securityContext,
|
||||
}
|
||||
|
||||
if livenessProbe != nil {
|
||||
|
|
|
@ -61,6 +61,10 @@ func (a *ArangoDContainer) GetExecutor() string {
|
|||
return ArangoDExecutor
|
||||
}
|
||||
|
||||
func (a *ArangoDContainer) GetSecurityContext() *v1.SecurityContext {
|
||||
return a.groupSpec.SecurityContext.NewSecurityContext()
|
||||
}
|
||||
|
||||
func (a *ArangoDContainer) GetProbes() (*v1.Probe, *v1.Probe, error) {
|
||||
var liveness, readiness *v1.Probe
|
||||
|
||||
|
@ -169,7 +173,8 @@ func (m *MemberArangoDPod) GetSidecars(pod *v1.Pod) {
|
|||
}
|
||||
|
||||
c := ArangodbExporterContainer(image, createExporterArgs(m.spec.IsSecure()),
|
||||
createExporterLivenessProbe(m.spec.IsSecure()), m.spec.Metrics.Resources)
|
||||
createExporterLivenessProbe(m.spec.IsSecure()), m.spec.Metrics.Resources,
|
||||
m.groupSpec.SecurityContext.NewSecurityContext())
|
||||
|
||||
if m.spec.Metrics.GetJWTTokenSecretName() != "" {
|
||||
c.VolumeMounts = append(c.VolumeMounts, k8sutil.ExporterJWTVolumeMount())
|
||||
|
@ -253,7 +258,8 @@ func (m *MemberArangoDPod) GetInitContainers() ([]v1.Container, error) {
|
|||
|
||||
lifecycleImage := m.resources.context.GetLifecycleImage()
|
||||
if lifecycleImage != "" {
|
||||
c, err := k8sutil.InitLifecycleContainer(lifecycleImage, &m.spec.Lifecycle.Resources)
|
||||
c, err := k8sutil.InitLifecycleContainer(lifecycleImage, &m.spec.Lifecycle.Resources,
|
||||
m.groupSpec.SecurityContext.NewSecurityContext())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -265,7 +271,8 @@ func (m *MemberArangoDPod) GetInitContainers() ([]v1.Container, error) {
|
|||
engine := m.spec.GetStorageEngine().AsArangoArgument()
|
||||
requireUUID := m.group == api.ServerGroupDBServers && m.status.IsInitialized
|
||||
|
||||
c := k8sutil.ArangodInitContainer("uuid", m.status.ID, engine, alpineImage, requireUUID)
|
||||
c := k8sutil.ArangodInitContainer("uuid", m.status.ID, engine, alpineImage, requireUUID,
|
||||
m.groupSpec.SecurityContext.NewSecurityContext())
|
||||
initContainers = append(initContainers, c)
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,10 @@ func (a *ArangoSyncContainer) GetExecutor() string {
|
|||
return ArangoSyncExecutor
|
||||
}
|
||||
|
||||
func (a *ArangoSyncContainer) GetSecurityContext() *v1.SecurityContext {
|
||||
return a.groupSpec.SecurityContext.NewSecurityContext()
|
||||
}
|
||||
|
||||
func (a *ArangoSyncContainer) GetProbes() (*v1.Probe, *v1.Probe, error) {
|
||||
var liveness, readiness *v1.Probe
|
||||
|
||||
|
@ -206,7 +210,8 @@ func (m *MemberSyncPod) GetInitContainers() ([]v1.Container, error) {
|
|||
|
||||
lifecycleImage := m.resources.context.GetLifecycleImage()
|
||||
if lifecycleImage != "" {
|
||||
c, err := k8sutil.InitLifecycleContainer(lifecycleImage, &m.spec.Lifecycle.Resources)
|
||||
c, err := k8sutil.InitLifecycleContainer(lifecycleImage, &m.spec.Lifecycle.Resources,
|
||||
m.groupSpec.SecurityContext.NewSecurityContext())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ const (
|
|||
)
|
||||
|
||||
// InitLifecycleContainer creates an init-container to copy the lifecycle binary to a shared volume.
|
||||
func InitLifecycleContainer(image string, resources *v1.ResourceRequirements) (v1.Container, error) {
|
||||
func InitLifecycleContainer(image string, resources *v1.ResourceRequirements, securityContext *v1.SecurityContext) (v1.Container, error) {
|
||||
binaryPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return v1.Container{}, maskAny(err)
|
||||
|
@ -51,7 +51,7 @@ func InitLifecycleContainer(image string, resources *v1.ResourceRequirements) (v
|
|||
LifecycleVolumeMount(),
|
||||
},
|
||||
ImagePullPolicy: v1.PullIfNotPresent,
|
||||
SecurityContext: SecurityContextWithoutCapabilities(),
|
||||
SecurityContext: securityContext,
|
||||
}
|
||||
|
||||
if resources != nil {
|
||||
|
|
|
@ -77,6 +77,7 @@ type ContainerCreator interface {
|
|||
GetImagePullPolicy() v1.PullPolicy
|
||||
GetImage() string
|
||||
GetEnvs() []v1.EnvVar
|
||||
GetSecurityContext() *v1.SecurityContext
|
||||
}
|
||||
|
||||
// IsPodReady returns true if the PodReady condition on
|
||||
|
@ -258,7 +259,7 @@ func RocksdbEncryptionVolumeMount() v1.VolumeMount {
|
|||
}
|
||||
|
||||
// ArangodInitContainer creates a container configured to initalize a UUID file.
|
||||
func ArangodInitContainer(name, id, engine, alpineImage string, requireUUID bool) v1.Container {
|
||||
func ArangodInitContainer(name, id, engine, alpineImage string, requireUUID bool, securityContext *v1.SecurityContext) v1.Container {
|
||||
uuidFile := filepath.Join(ArangodVolumeMountDir, "UUID")
|
||||
engineFile := filepath.Join(ArangodVolumeMountDir, "ENGINE")
|
||||
var command string
|
||||
|
@ -296,7 +297,7 @@ func ArangodInitContainer(name, id, engine, alpineImage string, requireUUID bool
|
|||
VolumeMounts: []v1.VolumeMount{
|
||||
ArangodVolumeMount(),
|
||||
},
|
||||
SecurityContext: SecurityContextWithoutCapabilities(),
|
||||
SecurityContext: securityContext,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
@ -351,7 +352,7 @@ func NewContainer(args []string, containerCreator ContainerCreator) (v1.Containe
|
|||
ReadinessProbe: readiness,
|
||||
Lifecycle: lifecycle,
|
||||
ImagePullPolicy: containerCreator.GetImagePullPolicy(),
|
||||
SecurityContext: SecurityContextWithoutCapabilities(),
|
||||
SecurityContext: containerCreator.GetSecurityContext(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -401,14 +402,6 @@ func CreatePod(kubecli kubernetes.Interface, pod *v1.Pod, ns string, owner metav
|
|||
return nil
|
||||
}
|
||||
|
||||
func SecurityContextWithoutCapabilities() *v1.SecurityContext {
|
||||
return &v1.SecurityContext{
|
||||
Capabilities: &v1.Capabilities{
|
||||
Drop: []v1.Capability{"ALL"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateVolumeEmptyDir(name string) v1.Volume {
|
||||
return v1.Volume{
|
||||
Name: name,
|
||||
|
|
|
@ -173,7 +173,6 @@ func runVolumeInspector(ctx context.Context, kube kubernetes.Interface, ns, name
|
|||
},
|
||||
},
|
||||
},
|
||||
SecurityContext: k8sutil.SecurityContextWithoutCapabilities(),
|
||||
},
|
||||
},
|
||||
Volumes: []corev1.Volume{
|
||||
|
|
Loading…
Reference in a new issue