mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
[Feature] Add secured contatiners' features (#1287)
This commit is contained in:
parent
7ad5d275b4
commit
a221ce9b6e
17 changed files with 712 additions and 43 deletions
|
@ -3,6 +3,7 @@
|
|||
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
|
||||
- (Feature) Backup lifetime - remove Backup once its lifetime has been reached
|
||||
- (Feature) Add Feature dependency
|
||||
- (Feature) Run secured containers as a feature
|
||||
|
||||
## [1.2.31](https://github.com/arangodb/kube-arangodb/tree/1.2.31) (2023-07-14)
|
||||
- (Improvement) Block traffic on the services if there is more than 1 active leader in ActiveFailover mode
|
||||
|
|
|
@ -68,11 +68,13 @@ covers individual newer features separately.
|
|||
| Graceful Restart | 1.2.5 | >= 3.6.0 | Community, Enterprise | 1.0.7 | Production | True | --deployment.feature.graceful-shutdown | N/A |
|
||||
| Optional Graceful Restart | 1.2.25 | >= 3.6.0 | Community, Enterprise | 1.2.5 | Beta | True | --deployment.feature.optional-graceful-shutdown | N/A |
|
||||
| Operator Internal Metrics Exporter | 1.2.0 | >= 3.6.0 | Community, Enterprise | 1.2.0 | Production | True | --deployment.feature.metrics-exporter | N/A |
|
||||
| Operator Ephemeral Volumes | 1.2.2 | >= 3.7.0 | Community, Enterprise | 1.2.2 | Alpha | False | --deployment.feature.ephemeral-volumes | N/A |
|
||||
| [Operator Ephemeral Volumes](docs/design/features/ephemeral_volumes.md) | 1.2.2 | >= 3.7.0 | Community, Enterprise | 1.2.2 | Alpha | False | --deployment.feature.ephemeral-volumes | N/A |
|
||||
| [Operator Ephemeral Volumes](docs/design/features/ephemeral_volumes.md) | 1.2.2 | >= 3.7.0 | Community, Enterprise | 1.2.31 | Beta | False | --deployment.feature.ephemeral-volumes | N/A |
|
||||
| [Failover Leader service](docs/design/features/failover_leader_service.md) | 1.2.13 | >= 3.7.0 | Community, Enterprise | 1.2.13 | Production | False | --deployment.feature.failover-leadership | N/A |
|
||||
| [Spec Default Restore](docs/design/features/deployment_spec_defaults.md) | 1.2.21 | >= 3.7.0 | Community, Enterprise | 1.2.21 | Beta | True | --deployment.feature.deployment-spec-defaults-restore | If set to False Operator will not change ArangoDeployment Spec |
|
||||
| [Force Rebuild Out Synced Shards](docs/design/features/rebuild_out_synced_shards.md) | 1.2.27 | >= 3.8.0 | Community, Enterprise | 1.2.27 | Beta | False | --deployment.feature.force-rebuild-out-synced-shards | It should be used only if user is aware of the risks. |
|
||||
| [Rebalancer V2](docs/design/features/rebalancer_v2.md) | 1.2.31 | >= 3.10.0 | Community, Enterprise | 1.2.31 | Alpha | False | --deployment.feature.rebalancer-v2 | N/A |
|
||||
| [Secured containers](docs/design/features/secured_containers.md) | 1.2.31 | >= 3.7.0 | Community, Enterprise | 1.2.31 | Alpha | False | --deployment.feature.secured-containers | If set to True Operator will run containers in secure mode |
|
||||
|
||||
## Operator Community Edition (CE)
|
||||
|
||||
|
|
20
docs/design/features/ephemeral_volumes.md
Normal file
20
docs/design/features/ephemeral_volumes.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Operator Ephemeral Volumes
|
||||
|
||||
## Overview
|
||||
|
||||
Operator add 2 EmptyDir mounts to the ArangoDB Pods:
|
||||
|
||||
- `ephemeral-apps` which is mounted under `/ephemeral/app` and passed to the ArangoDB process via `--javascript.app-path` arg
|
||||
- `ephemeral-tmp` which is mounted under `/ephemeral/tmp` and passed to the ArangoDB process via `--temp.path` arg
|
||||
|
||||
This adds possibility to enable ReadOnly FileSystem via PodSecurityContext configuration.
|
||||
|
||||
## How to use
|
||||
|
||||
To enable this feature use `--deployment.feature.ephemeral-volumes` arg, which needs be passed to the operator:
|
||||
|
||||
```shell
|
||||
helm upgrade --install kube-arangodb \
|
||||
https://github.com/arangodb/kube-arangodb/releases/download/$VER/kube-arangodb-$VER.tgz \
|
||||
--set "operator.args={--deployment.feature.ephemeral-volumes}"
|
||||
```
|
28
docs/design/features/secured_containers.md
Normal file
28
docs/design/features/secured_containers.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Secured Containers
|
||||
|
||||
## Overview
|
||||
|
||||
Change Default settings of:
|
||||
|
||||
* PodSecurityContext
|
||||
* `FSGroup` is set to `3000`
|
||||
* SecurityContext (Container)
|
||||
* `RunAsUser` is set to `1000`
|
||||
* `RunAsGroup` is set to `2000`
|
||||
* `RunAsNonRoot` is set to `true`
|
||||
* `ReadOnlyRootFilesystem` is set to `true`
|
||||
* `Capabilities.Drop` is set to `["ALL"]`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [Operator Ephemeral Volumes](./ephemeral_volumes.md) should be Enabled and Supported.
|
||||
|
||||
## How to use
|
||||
|
||||
To enable this feature use `--deployment.feature.secured-containers` arg, which needs be passed to the operator:
|
||||
|
||||
```shell
|
||||
helm upgrade --install kube-arangodb \
|
||||
https://github.com/arangodb/kube-arangodb/releases/download/$VER/kube-arangodb-$VER.tgz \
|
||||
--set "operator.args={--deployment.feature.secured-containers}"
|
||||
```
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2016-2023 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.
|
||||
|
@ -20,7 +20,17 @@
|
|||
|
||||
package v1
|
||||
|
||||
import core "k8s.io/api/core/v1"
|
||||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRunAsUser = 1000
|
||||
defaultRunAsGroup = 2000
|
||||
defaultFSGroup = 3000
|
||||
)
|
||||
|
||||
// ServerGroupSpecSecurityContext contains specification for pod security context
|
||||
type ServerGroupSpecSecurityContext struct {
|
||||
|
@ -69,24 +79,31 @@ func (s *ServerGroupSpecSecurityContext) GetAddCapabilities() []core.Capability
|
|||
return s.AddCapabilities
|
||||
}
|
||||
|
||||
// NewSecurityContext creates new pod security context
|
||||
func (s *ServerGroupSpecSecurityContext) NewPodSecurityContext() *core.PodSecurityContext {
|
||||
if s == nil {
|
||||
return nil
|
||||
// NewPodSecurityContext creates new pod security context
|
||||
func (s *ServerGroupSpecSecurityContext) NewPodSecurityContext(secured bool) *core.PodSecurityContext {
|
||||
var psc *core.PodSecurityContext
|
||||
if s != nil && (s.FSGroup != nil || len(s.SupplementalGroups) > 0) {
|
||||
psc = &core.PodSecurityContext{
|
||||
SupplementalGroups: s.SupplementalGroups,
|
||||
FSGroup: s.FSGroup,
|
||||
}
|
||||
}
|
||||
|
||||
if s.FSGroup == nil && len(s.SupplementalGroups) == 0 {
|
||||
return nil
|
||||
if secured {
|
||||
if psc == nil {
|
||||
psc = &core.PodSecurityContext{}
|
||||
}
|
||||
|
||||
if psc.FSGroup == nil {
|
||||
psc.FSGroup = util.NewType[int64](defaultFSGroup)
|
||||
}
|
||||
}
|
||||
|
||||
return &core.PodSecurityContext{
|
||||
SupplementalGroups: s.SupplementalGroups,
|
||||
FSGroup: s.FSGroup,
|
||||
}
|
||||
return psc
|
||||
}
|
||||
|
||||
// NewSecurityContext creates new security context
|
||||
func (s *ServerGroupSpecSecurityContext) NewSecurityContext() *core.SecurityContext {
|
||||
func (s *ServerGroupSpecSecurityContext) NewSecurityContext(secured ...bool) *core.SecurityContext {
|
||||
r := &core.SecurityContext{}
|
||||
|
||||
if s != nil {
|
||||
|
@ -115,6 +132,27 @@ func (s *ServerGroupSpecSecurityContext) NewSecurityContext() *core.SecurityCont
|
|||
capabilities.Add = append(capabilities.Add, caps...)
|
||||
}
|
||||
|
||||
if len(secured) > 0 && secured[0] {
|
||||
if r.RunAsUser == nil {
|
||||
r.RunAsUser = util.NewType[int64](defaultRunAsUser)
|
||||
}
|
||||
if r.RunAsGroup == nil {
|
||||
r.RunAsGroup = util.NewType[int64](defaultRunAsGroup)
|
||||
}
|
||||
if r.RunAsNonRoot == nil {
|
||||
r.RunAsNonRoot = util.NewType[bool](true)
|
||||
}
|
||||
if r.ReadOnlyRootFilesystem == nil {
|
||||
r.ReadOnlyRootFilesystem = util.NewType[bool](true)
|
||||
}
|
||||
|
||||
if capabilities.Drop == nil {
|
||||
capabilities.Drop = []core.Capability{
|
||||
"ALL",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.Capabilities = capabilities
|
||||
|
||||
return r
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2023 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
|
||||
//
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func TestServerGroupSpecSecurityContext_NewPodSecurityContext(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
sc *ServerGroupSpecSecurityContext
|
||||
secured bool
|
||||
want *core.PodSecurityContext
|
||||
}{
|
||||
"default unsecured pod security": {
|
||||
sc: nil,
|
||||
want: nil,
|
||||
},
|
||||
"default secured pod security": {
|
||||
sc: nil,
|
||||
secured: true,
|
||||
want: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](defaultFSGroup),
|
||||
},
|
||||
},
|
||||
"user secured pod security takes precedence": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
FSGroup: util.NewType[int64](3001),
|
||||
},
|
||||
secured: true,
|
||||
want: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](3001),
|
||||
},
|
||||
},
|
||||
"user secured pod security with FSGroup==nil": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
SupplementalGroups: []int64{1},
|
||||
},
|
||||
secured: true,
|
||||
want: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](defaultFSGroup),
|
||||
SupplementalGroups: []int64{1},
|
||||
},
|
||||
},
|
||||
"user unsecured pod security": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
FSGroup: util.NewType[int64](3001),
|
||||
SupplementalGroups: []int64{1},
|
||||
},
|
||||
secured: false,
|
||||
want: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](3001),
|
||||
SupplementalGroups: []int64{1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
actual := testCase.sc.NewPodSecurityContext(testCase.secured)
|
||||
assert.Equalf(t, testCase.want, actual, "NewPodSecurityContext(%v)", testCase.secured)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerGroupSpecSecurityContext_NewSecurityContext(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
sc *ServerGroupSpecSecurityContext
|
||||
secured bool
|
||||
want *core.SecurityContext
|
||||
}{
|
||||
"default unsecured context security": {
|
||||
sc: nil,
|
||||
secured: false,
|
||||
want: &core.SecurityContext{
|
||||
Capabilities: &core.Capabilities{
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default secured context security": {
|
||||
sc: nil,
|
||||
secured: true,
|
||||
want: &core.SecurityContext{
|
||||
Capabilities: &core.Capabilities{
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
ReadOnlyRootFilesystem: util.NewType(true),
|
||||
RunAsGroup: util.NewType[int64](defaultRunAsGroup),
|
||||
RunAsNonRoot: util.NewType(true),
|
||||
RunAsUser: util.NewType[int64](defaultRunAsUser),
|
||||
},
|
||||
},
|
||||
"user unsecured context security": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
secured: false,
|
||||
want: &core.SecurityContext{
|
||||
Capabilities: &core.Capabilities{
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
},
|
||||
"secured user setting RunAsUser takes precedence": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
secured: true,
|
||||
want: &core.SecurityContext{
|
||||
Capabilities: &core.Capabilities{
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
ReadOnlyRootFilesystem: util.NewType(true),
|
||||
RunAsGroup: util.NewType[int64](defaultRunAsGroup),
|
||||
RunAsNonRoot: util.NewType(true),
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
},
|
||||
"secured mixed users' settings takes precedence": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
AddCapabilities: []core.Capability{"1"},
|
||||
AllowPrivilegeEscalation: util.NewType(true),
|
||||
DropAllCapabilities: util.NewType(false), // secured will turn it on
|
||||
Privileged: util.NewType(false),
|
||||
RunAsNonRoot: util.NewType(false),
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
secured: true,
|
||||
want: &core.SecurityContext{
|
||||
|
||||
AllowPrivilegeEscalation: util.NewType(true),
|
||||
Capabilities: &core.Capabilities{
|
||||
Add: []core.Capability{"1"},
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
Privileged: util.NewType(false),
|
||||
ReadOnlyRootFilesystem: util.NewType(true),
|
||||
RunAsGroup: util.NewType[int64](defaultRunAsGroup),
|
||||
RunAsNonRoot: util.NewType(false),
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range tests {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
actual := testCase.sc.NewSecurityContext(testCase.secured)
|
||||
assert.Equalf(t, testCase.want, actual, "NewSecurityContext(%v)", testCase.secured)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2016-2023 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.
|
||||
|
@ -20,7 +20,17 @@
|
|||
|
||||
package v2alpha1
|
||||
|
||||
import core "k8s.io/api/core/v1"
|
||||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRunAsUser = 1000
|
||||
defaultRunAsGroup = 2000
|
||||
defaultFSGroup = 3000
|
||||
)
|
||||
|
||||
// ServerGroupSpecSecurityContext contains specification for pod security context
|
||||
type ServerGroupSpecSecurityContext struct {
|
||||
|
@ -69,24 +79,31 @@ func (s *ServerGroupSpecSecurityContext) GetAddCapabilities() []core.Capability
|
|||
return s.AddCapabilities
|
||||
}
|
||||
|
||||
// NewSecurityContext creates new pod security context
|
||||
func (s *ServerGroupSpecSecurityContext) NewPodSecurityContext() *core.PodSecurityContext {
|
||||
if s == nil {
|
||||
return nil
|
||||
// NewPodSecurityContext creates new pod security context
|
||||
func (s *ServerGroupSpecSecurityContext) NewPodSecurityContext(secured bool) *core.PodSecurityContext {
|
||||
var psc *core.PodSecurityContext
|
||||
if s != nil && (s.FSGroup != nil || len(s.SupplementalGroups) > 0) {
|
||||
psc = &core.PodSecurityContext{
|
||||
SupplementalGroups: s.SupplementalGroups,
|
||||
FSGroup: s.FSGroup,
|
||||
}
|
||||
}
|
||||
|
||||
if s.FSGroup == nil && len(s.SupplementalGroups) == 0 {
|
||||
return nil
|
||||
if secured {
|
||||
if psc == nil {
|
||||
psc = &core.PodSecurityContext{}
|
||||
}
|
||||
|
||||
if psc.FSGroup == nil {
|
||||
psc.FSGroup = util.NewType[int64](defaultFSGroup)
|
||||
}
|
||||
}
|
||||
|
||||
return &core.PodSecurityContext{
|
||||
SupplementalGroups: s.SupplementalGroups,
|
||||
FSGroup: s.FSGroup,
|
||||
}
|
||||
return psc
|
||||
}
|
||||
|
||||
// NewSecurityContext creates new security context
|
||||
func (s *ServerGroupSpecSecurityContext) NewSecurityContext() *core.SecurityContext {
|
||||
func (s *ServerGroupSpecSecurityContext) NewSecurityContext(secured ...bool) *core.SecurityContext {
|
||||
r := &core.SecurityContext{}
|
||||
|
||||
if s != nil {
|
||||
|
@ -115,6 +132,27 @@ func (s *ServerGroupSpecSecurityContext) NewSecurityContext() *core.SecurityCont
|
|||
capabilities.Add = append(capabilities.Add, caps...)
|
||||
}
|
||||
|
||||
if len(secured) > 0 && secured[0] {
|
||||
if r.RunAsUser == nil {
|
||||
r.RunAsUser = util.NewType[int64](defaultRunAsUser)
|
||||
}
|
||||
if r.RunAsGroup == nil {
|
||||
r.RunAsGroup = util.NewType[int64](defaultRunAsGroup)
|
||||
}
|
||||
if r.RunAsNonRoot == nil {
|
||||
r.RunAsNonRoot = util.NewType[bool](true)
|
||||
}
|
||||
if r.ReadOnlyRootFilesystem == nil {
|
||||
r.ReadOnlyRootFilesystem = util.NewType[bool](true)
|
||||
}
|
||||
|
||||
if capabilities.Drop == nil {
|
||||
capabilities.Drop = []core.Capability{
|
||||
"ALL",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.Capabilities = capabilities
|
||||
|
||||
return r
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2023 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
|
||||
//
|
||||
|
||||
package v2alpha1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func TestServerGroupSpecSecurityContext_NewPodSecurityContext(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
sc *ServerGroupSpecSecurityContext
|
||||
secured bool
|
||||
want *core.PodSecurityContext
|
||||
}{
|
||||
"default unsecured pod security": {
|
||||
sc: nil,
|
||||
want: nil,
|
||||
},
|
||||
"default secured pod security": {
|
||||
sc: nil,
|
||||
secured: true,
|
||||
want: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](defaultFSGroup),
|
||||
},
|
||||
},
|
||||
"user secured pod security takes precedence": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
FSGroup: util.NewType[int64](3001),
|
||||
},
|
||||
secured: true,
|
||||
want: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](3001),
|
||||
},
|
||||
},
|
||||
"user secured pod security with FSGroup==nil": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
SupplementalGroups: []int64{1},
|
||||
},
|
||||
secured: true,
|
||||
want: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](defaultFSGroup),
|
||||
SupplementalGroups: []int64{1},
|
||||
},
|
||||
},
|
||||
"user unsecured pod security": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
FSGroup: util.NewType[int64](3001),
|
||||
SupplementalGroups: []int64{1},
|
||||
},
|
||||
secured: false,
|
||||
want: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](3001),
|
||||
SupplementalGroups: []int64{1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
actual := testCase.sc.NewPodSecurityContext(testCase.secured)
|
||||
assert.Equalf(t, testCase.want, actual, "NewPodSecurityContext(%v)", testCase.secured)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerGroupSpecSecurityContext_NewSecurityContext(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
sc *ServerGroupSpecSecurityContext
|
||||
secured bool
|
||||
want *core.SecurityContext
|
||||
}{
|
||||
"default unsecured context security": {
|
||||
sc: nil,
|
||||
secured: false,
|
||||
want: &core.SecurityContext{
|
||||
Capabilities: &core.Capabilities{
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default secured context security": {
|
||||
sc: nil,
|
||||
secured: true,
|
||||
want: &core.SecurityContext{
|
||||
Capabilities: &core.Capabilities{
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
ReadOnlyRootFilesystem: util.NewType(true),
|
||||
RunAsGroup: util.NewType[int64](defaultRunAsGroup),
|
||||
RunAsNonRoot: util.NewType(true),
|
||||
RunAsUser: util.NewType[int64](defaultRunAsUser),
|
||||
},
|
||||
},
|
||||
"user unsecured context security": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
secured: false,
|
||||
want: &core.SecurityContext{
|
||||
Capabilities: &core.Capabilities{
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
},
|
||||
"secured user setting RunAsUser takes precedence": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
secured: true,
|
||||
want: &core.SecurityContext{
|
||||
Capabilities: &core.Capabilities{
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
ReadOnlyRootFilesystem: util.NewType(true),
|
||||
RunAsGroup: util.NewType[int64](defaultRunAsGroup),
|
||||
RunAsNonRoot: util.NewType(true),
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
},
|
||||
"secured mixed users' settings takes precedence": {
|
||||
sc: &ServerGroupSpecSecurityContext{
|
||||
AddCapabilities: []core.Capability{"1"},
|
||||
AllowPrivilegeEscalation: util.NewType(true),
|
||||
DropAllCapabilities: util.NewType(false), // secured will turn it on
|
||||
Privileged: util.NewType(false),
|
||||
RunAsNonRoot: util.NewType(false),
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
secured: true,
|
||||
want: &core.SecurityContext{
|
||||
|
||||
AllowPrivilegeEscalation: util.NewType(true),
|
||||
Capabilities: &core.Capabilities{
|
||||
Add: []core.Capability{"1"},
|
||||
Drop: []core.Capability{"ALL"},
|
||||
},
|
||||
Privileged: util.NewType(false),
|
||||
ReadOnlyRootFilesystem: util.NewType(true),
|
||||
RunAsGroup: util.NewType[int64](defaultRunAsGroup),
|
||||
RunAsNonRoot: util.NewType(false),
|
||||
RunAsUser: util.NewType[int64](3001),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range tests {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
actual := testCase.sc.NewSecurityContext(testCase.secured)
|
||||
assert.Equalf(t, testCase.want, actual, "NewSecurityContext(%v)", testCase.secured)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -68,6 +68,7 @@ type Feature interface {
|
|||
Hidden() bool
|
||||
Supported(v driver.Version, enterprise bool) bool
|
||||
ImageSupported(i *api.ImageInfo) bool
|
||||
GetDependencies() []string
|
||||
}
|
||||
|
||||
type feature struct {
|
||||
|
@ -104,6 +105,20 @@ func (f feature) Hidden() bool {
|
|||
return f.hidden
|
||||
}
|
||||
|
||||
// GetDependencies returns direct dependencies' names of features.
|
||||
func (f feature) GetDependencies() []string {
|
||||
if len(f.dependencies) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
deps := make([]string, 0, len(f.dependencies))
|
||||
for _, dependency := range f.dependencies {
|
||||
deps = append(deps, dependency.Name())
|
||||
}
|
||||
|
||||
return deps
|
||||
}
|
||||
|
||||
func (f feature) Supported(v driver.Version, enterprise bool) bool {
|
||||
return Supported(&f, v, enterprise)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
|
||||
"github.com/arangodb/go-driver"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/logging"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
|
@ -119,9 +120,54 @@ func Init(cmd *cobra.Command) error {
|
|||
|
||||
f.StringVar(&configMapName, "features-config-map-name", DefaultFeaturesConfigMap, "Name of the Feature Map ConfigMap")
|
||||
|
||||
checkDependencies(cmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDependencies(cmd *cobra.Command) {
|
||||
|
||||
enableDeps := func(_ *cobra.Command, _ []string) {
|
||||
// Turn on dependencies. This function will be called when all process's arguments are passed, so
|
||||
// all required features are enabled and dependencies should be enabled too.
|
||||
EnableDependencies()
|
||||
|
||||
// Log enabled features when process starts.
|
||||
for _, f := range features {
|
||||
if !f.Enabled() {
|
||||
continue
|
||||
}
|
||||
|
||||
l := logging.Global().RegisterAndGetLogger("features", logging.Info)
|
||||
if deps := f.GetDependencies(); len(deps) > 0 {
|
||||
l = l.Strs("dependencies", deps...)
|
||||
}
|
||||
|
||||
l.Bool("enterpriseArangoDBRequired", f.EnterpriseRequired()).
|
||||
Str("minArangoDBVersion", string(f.Version())).
|
||||
Str("name", f.Name()).
|
||||
Info("feature enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap pre-run function if it set.
|
||||
if cmd.PreRunE != nil {
|
||||
local := cmd.PreRunE
|
||||
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
enableDeps(cmd, args)
|
||||
return local(cmd, args)
|
||||
}
|
||||
} else if cmd.PreRun != nil {
|
||||
local := cmd.PreRun
|
||||
cmd.PreRun = func(cmd *cobra.Command, args []string) {
|
||||
enableDeps(cmd, args)
|
||||
local(cmd, args)
|
||||
}
|
||||
} else {
|
||||
cmd.PreRun = enableDeps
|
||||
}
|
||||
}
|
||||
|
||||
func cmdRun(_ *cobra.Command, _ []string) {
|
||||
featuresLock.Lock()
|
||||
defer featuresLock.Unlock()
|
||||
|
@ -151,6 +197,10 @@ func cmdRun(_ *cobra.Command, _ []string) {
|
|||
println("ArangoDB Edition Required: Community, Enterprise")
|
||||
}
|
||||
|
||||
if deps := feature.GetDependencies(); len(deps) > 0 {
|
||||
println(fmt.Sprintf("Dependencies: %v", deps))
|
||||
}
|
||||
|
||||
if ok, reason := feature.Deprecated(); ok {
|
||||
println(fmt.Sprintf("Deprecated: %s", reason))
|
||||
}
|
||||
|
@ -202,3 +252,45 @@ func GetFeatureArgName(featureName string) string {
|
|||
func isEnabledFeatureFromEnv(arg string) bool {
|
||||
return os.Getenv(util.NormalizeEnv(arg)) == Enabled
|
||||
}
|
||||
|
||||
// EnableDependencies enables dependencies for features if it is required.
|
||||
func EnableDependencies() {
|
||||
for {
|
||||
var changed bool
|
||||
|
||||
for _, f := range features {
|
||||
if !f.Enabled() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, depName := range f.GetDependencies() {
|
||||
// Don't use `dependency.Enabled` here because `constValue` is involved here, and it can not be changed.
|
||||
if enableDependencyByName(depName) {
|
||||
// Dependency is changed so list of features must be iterated once again, because this
|
||||
// dependency can turn on other dependencies.
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enableDependencyByName enables dependency by name of a feature.
|
||||
func enableDependencyByName(name string) bool {
|
||||
for _, f := range features {
|
||||
if name != f.Name() {
|
||||
continue
|
||||
}
|
||||
|
||||
if ep := f.EnabledPointer(); !*ep {
|
||||
*ep = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
44
pkg/deployment/features/secured_containers.go
Normal file
44
pkg/deployment/features/secured_containers.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2023 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
|
||||
//
|
||||
|
||||
package features
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerFeature(securedContainers)
|
||||
}
|
||||
|
||||
var securedContainers = &feature{
|
||||
name: "secured-containers",
|
||||
description: fmt.Sprintf("Create server's containers with non root privileges. "+
|
||||
"It enables '%s' feature implicitly", ephemeralVolumes.Name()),
|
||||
version: "3.7.0",
|
||||
enterpriseRequired: false,
|
||||
enabledByDefault: false,
|
||||
dependencies: []Feature{ephemeralVolumes},
|
||||
}
|
||||
|
||||
// SecuredContainers returns secured containers feature.
|
||||
func SecuredContainers() Feature {
|
||||
return securedContainers
|
||||
}
|
|
@ -388,7 +388,7 @@ func (i *ImageUpdatePod) Validate(_ interfaces.Inspector) error {
|
|||
|
||||
func (i *ImageUpdatePod) ApplyPodSpec(p *core.PodSpec) error {
|
||||
if id := i.spec.ID; id != nil {
|
||||
p.SecurityContext = i.spec.ID.SecurityContext.NewPodSecurityContext()
|
||||
p.SecurityContext = k8sutil.CreatePodSecurityContext(i.spec.ID.SecurityContext)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -441,7 +441,7 @@ func (a *ContainerIdentity) GetResourceRequirements() core.ResourceRequirements
|
|||
}
|
||||
|
||||
func (a *ContainerIdentity) GetSecurityContext() *core.SecurityContext {
|
||||
return a.ID.Get().SecurityContext.NewSecurityContext()
|
||||
return k8sutil.CreateSecurityContext(a.ID.Get().SecurityContext)
|
||||
}
|
||||
|
||||
// GetVolumeMounts returns nil for the basic container identity.
|
||||
|
|
|
@ -39,8 +39,8 @@ func (s security) Args(i Input) k8sutil.OptionPairs {
|
|||
opts := k8sutil.CreateOptionPairs()
|
||||
|
||||
if features.EphemeralVolumes().Enabled() {
|
||||
opts.Add("--temp.path", "/ephemeral/app")
|
||||
opts.Add("--javascript.app-path", "/ephemeral/tmp")
|
||||
opts.Add("--temp.path", "/ephemeral/tmp")
|
||||
opts.Add("--javascript.app-path", "/ephemeral/app")
|
||||
}
|
||||
|
||||
return opts
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2016-2023 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.
|
||||
|
@ -55,8 +55,8 @@ func ArangodbInternalExporterContainer(image string, args []string, livenessProb
|
|||
},
|
||||
},
|
||||
Resources: k8sutil.ExtractPodResourceRequirement(resources),
|
||||
SecurityContext: k8sutil.CreateSecurityContext(groupSpec.SecurityContext),
|
||||
ImagePullPolicy: core.PullIfNotPresent,
|
||||
SecurityContext: groupSpec.SecurityContext.NewSecurityContext(),
|
||||
VolumeMounts: []core.VolumeMount{k8sutil.LifecycleVolumeMount()},
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ func (a *ArangoDContainer) GetExecutor() string {
|
|||
}
|
||||
|
||||
func (a *ArangoDContainer) GetSecurityContext() *core.SecurityContext {
|
||||
return a.groupSpec.SecurityContext.NewSecurityContext()
|
||||
return k8sutil.CreateSecurityContext(a.groupSpec.SecurityContext)
|
||||
}
|
||||
|
||||
func (a *ArangoDContainer) GetProbes() (*core.Probe, *core.Probe, *core.Probe, error) {
|
||||
|
@ -435,8 +435,8 @@ func (m *MemberArangoDPod) GetInitContainers(cachedStatus interfaces.Inspector)
|
|||
}
|
||||
|
||||
{
|
||||
c, err := k8sutil.InitLifecycleContainer(m.resources.context.GetOperatorImage(), &m.spec.Lifecycle.Resources,
|
||||
m.groupSpec.SecurityContext.NewSecurityContext())
|
||||
sc := k8sutil.CreateSecurityContext(m.groupSpec.SecurityContext)
|
||||
c, err := k8sutil.InitLifecycleContainer(m.resources.context.GetOperatorImage(), &m.spec.Lifecycle.Resources, sc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -447,8 +447,9 @@ func (m *MemberArangoDPod) GetInitContainers(cachedStatus interfaces.Inspector)
|
|||
engine := m.spec.GetStorageEngine().AsArangoArgument()
|
||||
requireUUID := m.group == api.ServerGroupDBServers && m.status.IsInitialized
|
||||
|
||||
c := k8sutil.ArangodInitContainer(api.ServerGroupReservedInitContainerNameUUID, m.status.ID, engine, executable, m.resources.context.GetOperatorImage(), requireUUID,
|
||||
m.groupSpec.SecurityContext.NewSecurityContext())
|
||||
sc := k8sutil.CreateSecurityContext(m.groupSpec.SecurityContext)
|
||||
c := k8sutil.ArangodInitContainer(api.ServerGroupReservedInitContainerNameUUID, m.status.ID, engine, executable,
|
||||
m.resources.context.GetOperatorImage(), requireUUID, sc)
|
||||
initContainers = append(initContainers, c)
|
||||
}
|
||||
|
||||
|
@ -551,8 +552,7 @@ func (m *MemberArangoDPod) createMetricsExporterSidecarInternalExporter() (*core
|
|||
}
|
||||
|
||||
func (m *MemberArangoDPod) ApplyPodSpec(p *core.PodSpec) error {
|
||||
p.SecurityContext = m.groupSpec.SecurityContext.NewPodSecurityContext()
|
||||
|
||||
p.SecurityContext = k8sutil.CreatePodSecurityContext(m.groupSpec.SecurityContext)
|
||||
if s := m.groupSpec.SchedulerName; s != nil {
|
||||
p.SchedulerName = *s
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ func (a *ArangoSyncContainer) GetExecutor() string {
|
|||
}
|
||||
|
||||
func (a *ArangoSyncContainer) GetSecurityContext() *core.SecurityContext {
|
||||
return a.groupSpec.SecurityContext.NewSecurityContext()
|
||||
return k8sutil.CreateSecurityContext(a.groupSpec.SecurityContext)
|
||||
}
|
||||
|
||||
func (a *ArangoSyncContainer) GetProbes() (*core.Probe, *core.Probe, *core.Probe, error) {
|
||||
|
@ -291,8 +291,8 @@ func (m *MemberSyncPod) GetInitContainers(cachedStatus interfaces.Inspector) ([]
|
|||
}
|
||||
|
||||
{
|
||||
c, err := k8sutil.InitLifecycleContainer(m.resources.context.GetOperatorImage(), &m.spec.Lifecycle.Resources,
|
||||
m.groupSpec.SecurityContext.NewSecurityContext())
|
||||
sc := k8sutil.CreateSecurityContext(m.groupSpec.SecurityContext)
|
||||
c, err := k8sutil.InitLifecycleContainer(m.resources.context.GetOperatorImage(), &m.spec.Lifecycle.Resources, sc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
39
pkg/util/k8sutil/security_context.go
Normal file
39
pkg/util/k8sutil/security_context.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2023 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
|
||||
//
|
||||
|
||||
package k8sutil
|
||||
|
||||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
|
||||
)
|
||||
|
||||
// CreateSecurityContext returns security context.
|
||||
// If secured container's feature is enabled then default values will set on nil fields.
|
||||
func CreateSecurityContext(spec *api.ServerGroupSpecSecurityContext) *core.SecurityContext {
|
||||
return spec.NewSecurityContext(features.SecuredContainers().Enabled())
|
||||
}
|
||||
|
||||
// CreatePodSecurityContext creates pod's security context.
|
||||
func CreatePodSecurityContext(spec *api.ServerGroupSpecSecurityContext) *core.PodSecurityContext {
|
||||
return spec.NewPodSecurityContext(features.SecuredContainers().Enabled())
|
||||
}
|
Loading…
Reference in a new issue