1
0
Fork 0
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:
Adam Janikowski 2020-03-02 11:03:45 +01:00 committed by GitHub
parent 3d8b77e0d6
commit 40d738b771
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 286 additions and 20 deletions

View file

@ -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<=

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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)

View 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
}

View file

@ -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 {

View file

@ -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)
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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,

View file

@ -173,7 +173,6 @@ func runVolumeInspector(ctx context.Context, kube kubernetes.Interface, ns, name
},
},
},
SecurityContext: k8sutil.SecurityContextWithoutCapabilities(),
},
},
Volumes: []corev1.Volume{