Skip to content

Commit a221ce9

Browse files
authored
[Feature] Add secured contatiners' features (#1287)
1 parent 7ad5d27 commit a221ce9

17 files changed

+712
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
44
- (Feature) Backup lifetime - remove Backup once its lifetime has been reached
55
- (Feature) Add Feature dependency
6+
- (Feature) Run secured containers as a feature
67

78
## [1.2.31](https://github.com/arangodb/kube-arangodb/tree/1.2.31) (2023-07-14)
89
- (Improvement) Block traffic on the services if there is more than 1 active leader in ActiveFailover mode

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,13 @@ covers individual newer features separately.
6868
| Graceful Restart | 1.2.5 | >= 3.6.0 | Community, Enterprise | 1.0.7 | Production | True | --deployment.feature.graceful-shutdown | N/A |
6969
| Optional Graceful Restart | 1.2.25 | >= 3.6.0 | Community, Enterprise | 1.2.5 | Beta | True | --deployment.feature.optional-graceful-shutdown | N/A |
7070
| Operator Internal Metrics Exporter | 1.2.0 | >= 3.6.0 | Community, Enterprise | 1.2.0 | Production | True | --deployment.feature.metrics-exporter | N/A |
71-
| Operator Ephemeral Volumes | 1.2.2 | >= 3.7.0 | Community, Enterprise | 1.2.2 | Alpha | False | --deployment.feature.ephemeral-volumes | N/A |
71+
| [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 |
72+
| [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 |
7273
| [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 |
7374
| [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 |
7475
| [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. |
7576
| [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 |
77+
| [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 |
7678

7779
## Operator Community Edition (CE)
7880

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Operator Ephemeral Volumes
2+
3+
## Overview
4+
5+
Operator add 2 EmptyDir mounts to the ArangoDB Pods:
6+
7+
- `ephemeral-apps` which is mounted under `/ephemeral/app` and passed to the ArangoDB process via `--javascript.app-path` arg
8+
- `ephemeral-tmp` which is mounted under `/ephemeral/tmp` and passed to the ArangoDB process via `--temp.path` arg
9+
10+
This adds possibility to enable ReadOnly FileSystem via PodSecurityContext configuration.
11+
12+
## How to use
13+
14+
To enable this feature use `--deployment.feature.ephemeral-volumes` arg, which needs be passed to the operator:
15+
16+
```shell
17+
helm upgrade --install kube-arangodb \
18+
https://github.com/arangodb/kube-arangodb/releases/download/$VER/kube-arangodb-$VER.tgz \
19+
--set "operator.args={--deployment.feature.ephemeral-volumes}"
20+
```
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Secured Containers
2+
3+
## Overview
4+
5+
Change Default settings of:
6+
7+
* PodSecurityContext
8+
* `FSGroup` is set to `3000`
9+
* SecurityContext (Container)
10+
* `RunAsUser` is set to `1000`
11+
* `RunAsGroup` is set to `2000`
12+
* `RunAsNonRoot` is set to `true`
13+
* `ReadOnlyRootFilesystem` is set to `true`
14+
* `Capabilities.Drop` is set to `["ALL"]`
15+
16+
## Dependencies
17+
18+
- [Operator Ephemeral Volumes](./ephemeral_volumes.md) should be Enabled and Supported.
19+
20+
## How to use
21+
22+
To enable this feature use `--deployment.feature.secured-containers` arg, which needs be passed to the operator:
23+
24+
```shell
25+
helm upgrade --install kube-arangodb \
26+
https://github.com/arangodb/kube-arangodb/releases/download/$VER/kube-arangodb-$VER.tgz \
27+
--set "operator.args={--deployment.feature.secured-containers}"
28+
```

pkg/apis/deployment/v1/server_group_security_context_spec.go

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// DISCLAIMER
33
//
4-
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
4+
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
55
//
66
// Licensed under the Apache License, Version 2.0 (the "License");
77
// you may not use this file except in compliance with the License.
@@ -20,7 +20,17 @@
2020

2121
package v1
2222

23-
import core "k8s.io/api/core/v1"
23+
import (
24+
core "k8s.io/api/core/v1"
25+
26+
"github.com/arangodb/kube-arangodb/pkg/util"
27+
)
28+
29+
const (
30+
defaultRunAsUser = 1000
31+
defaultRunAsGroup = 2000
32+
defaultFSGroup = 3000
33+
)
2434

2535
// ServerGroupSpecSecurityContext contains specification for pod security context
2636
type ServerGroupSpecSecurityContext struct {
@@ -69,24 +79,31 @@ func (s *ServerGroupSpecSecurityContext) GetAddCapabilities() []core.Capability
6979
return s.AddCapabilities
7080
}
7181

72-
// NewSecurityContext creates new pod security context
73-
func (s *ServerGroupSpecSecurityContext) NewPodSecurityContext() *core.PodSecurityContext {
74-
if s == nil {
75-
return nil
82+
// NewPodSecurityContext creates new pod security context
83+
func (s *ServerGroupSpecSecurityContext) NewPodSecurityContext(secured bool) *core.PodSecurityContext {
84+
var psc *core.PodSecurityContext
85+
if s != nil && (s.FSGroup != nil || len(s.SupplementalGroups) > 0) {
86+
psc = &core.PodSecurityContext{
87+
SupplementalGroups: s.SupplementalGroups,
88+
FSGroup: s.FSGroup,
89+
}
7690
}
7791

78-
if s.FSGroup == nil && len(s.SupplementalGroups) == 0 {
79-
return nil
80-
}
92+
if secured {
93+
if psc == nil {
94+
psc = &core.PodSecurityContext{}
95+
}
8196

82-
return &core.PodSecurityContext{
83-
SupplementalGroups: s.SupplementalGroups,
84-
FSGroup: s.FSGroup,
97+
if psc.FSGroup == nil {
98+
psc.FSGroup = util.NewType[int64](defaultFSGroup)
99+
}
85100
}
101+
102+
return psc
86103
}
87104

88105
// NewSecurityContext creates new security context
89-
func (s *ServerGroupSpecSecurityContext) NewSecurityContext() *core.SecurityContext {
106+
func (s *ServerGroupSpecSecurityContext) NewSecurityContext(secured ...bool) *core.SecurityContext {
90107
r := &core.SecurityContext{}
91108

92109
if s != nil {
@@ -115,6 +132,27 @@ func (s *ServerGroupSpecSecurityContext) NewSecurityContext() *core.SecurityCont
115132
capabilities.Add = append(capabilities.Add, caps...)
116133
}
117134

135+
if len(secured) > 0 && secured[0] {
136+
if r.RunAsUser == nil {
137+
r.RunAsUser = util.NewType[int64](defaultRunAsUser)
138+
}
139+
if r.RunAsGroup == nil {
140+
r.RunAsGroup = util.NewType[int64](defaultRunAsGroup)
141+
}
142+
if r.RunAsNonRoot == nil {
143+
r.RunAsNonRoot = util.NewType[bool](true)
144+
}
145+
if r.ReadOnlyRootFilesystem == nil {
146+
r.ReadOnlyRootFilesystem = util.NewType[bool](true)
147+
}
148+
149+
if capabilities.Drop == nil {
150+
capabilities.Drop = []core.Capability{
151+
"ALL",
152+
}
153+
}
154+
}
155+
118156
r.Capabilities = capabilities
119157

120158
return r
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
21+
package v1
22+
23+
import (
24+
"testing"
25+
26+
"github.com/stretchr/testify/assert"
27+
core "k8s.io/api/core/v1"
28+
29+
"github.com/arangodb/kube-arangodb/pkg/util"
30+
)
31+
32+
func TestServerGroupSpecSecurityContext_NewPodSecurityContext(t *testing.T) {
33+
testCases := map[string]struct {
34+
sc *ServerGroupSpecSecurityContext
35+
secured bool
36+
want *core.PodSecurityContext
37+
}{
38+
"default unsecured pod security": {
39+
sc: nil,
40+
want: nil,
41+
},
42+
"default secured pod security": {
43+
sc: nil,
44+
secured: true,
45+
want: &core.PodSecurityContext{
46+
FSGroup: util.NewType[int64](defaultFSGroup),
47+
},
48+
},
49+
"user secured pod security takes precedence": {
50+
sc: &ServerGroupSpecSecurityContext{
51+
FSGroup: util.NewType[int64](3001),
52+
},
53+
secured: true,
54+
want: &core.PodSecurityContext{
55+
FSGroup: util.NewType[int64](3001),
56+
},
57+
},
58+
"user secured pod security with FSGroup==nil": {
59+
sc: &ServerGroupSpecSecurityContext{
60+
SupplementalGroups: []int64{1},
61+
},
62+
secured: true,
63+
want: &core.PodSecurityContext{
64+
FSGroup: util.NewType[int64](defaultFSGroup),
65+
SupplementalGroups: []int64{1},
66+
},
67+
},
68+
"user unsecured pod security": {
69+
sc: &ServerGroupSpecSecurityContext{
70+
FSGroup: util.NewType[int64](3001),
71+
SupplementalGroups: []int64{1},
72+
},
73+
secured: false,
74+
want: &core.PodSecurityContext{
75+
FSGroup: util.NewType[int64](3001),
76+
SupplementalGroups: []int64{1},
77+
},
78+
},
79+
}
80+
81+
for testName, testCase := range testCases {
82+
t.Run(testName, func(t *testing.T) {
83+
actual := testCase.sc.NewPodSecurityContext(testCase.secured)
84+
assert.Equalf(t, testCase.want, actual, "NewPodSecurityContext(%v)", testCase.secured)
85+
})
86+
}
87+
}
88+
89+
func TestServerGroupSpecSecurityContext_NewSecurityContext(t *testing.T) {
90+
tests := map[string]struct {
91+
sc *ServerGroupSpecSecurityContext
92+
secured bool
93+
want *core.SecurityContext
94+
}{
95+
"default unsecured context security": {
96+
sc: nil,
97+
secured: false,
98+
want: &core.SecurityContext{
99+
Capabilities: &core.Capabilities{
100+
Drop: []core.Capability{"ALL"},
101+
},
102+
},
103+
},
104+
"default secured context security": {
105+
sc: nil,
106+
secured: true,
107+
want: &core.SecurityContext{
108+
Capabilities: &core.Capabilities{
109+
Drop: []core.Capability{"ALL"},
110+
},
111+
ReadOnlyRootFilesystem: util.NewType(true),
112+
RunAsGroup: util.NewType[int64](defaultRunAsGroup),
113+
RunAsNonRoot: util.NewType(true),
114+
RunAsUser: util.NewType[int64](defaultRunAsUser),
115+
},
116+
},
117+
"user unsecured context security": {
118+
sc: &ServerGroupSpecSecurityContext{
119+
RunAsUser: util.NewType[int64](3001),
120+
},
121+
secured: false,
122+
want: &core.SecurityContext{
123+
Capabilities: &core.Capabilities{
124+
Drop: []core.Capability{"ALL"},
125+
},
126+
RunAsUser: util.NewType[int64](3001),
127+
},
128+
},
129+
"secured user setting RunAsUser takes precedence": {
130+
sc: &ServerGroupSpecSecurityContext{
131+
RunAsUser: util.NewType[int64](3001),
132+
},
133+
secured: true,
134+
want: &core.SecurityContext{
135+
Capabilities: &core.Capabilities{
136+
Drop: []core.Capability{"ALL"},
137+
},
138+
ReadOnlyRootFilesystem: util.NewType(true),
139+
RunAsGroup: util.NewType[int64](defaultRunAsGroup),
140+
RunAsNonRoot: util.NewType(true),
141+
RunAsUser: util.NewType[int64](3001),
142+
},
143+
},
144+
"secured mixed users' settings takes precedence": {
145+
sc: &ServerGroupSpecSecurityContext{
146+
AddCapabilities: []core.Capability{"1"},
147+
AllowPrivilegeEscalation: util.NewType(true),
148+
DropAllCapabilities: util.NewType(false), // secured will turn it on
149+
Privileged: util.NewType(false),
150+
RunAsNonRoot: util.NewType(false),
151+
RunAsUser: util.NewType[int64](3001),
152+
},
153+
secured: true,
154+
want: &core.SecurityContext{
155+
156+
AllowPrivilegeEscalation: util.NewType(true),
157+
Capabilities: &core.Capabilities{
158+
Add: []core.Capability{"1"},
159+
Drop: []core.Capability{"ALL"},
160+
},
161+
Privileged: util.NewType(false),
162+
ReadOnlyRootFilesystem: util.NewType(true),
163+
RunAsGroup: util.NewType[int64](defaultRunAsGroup),
164+
RunAsNonRoot: util.NewType(false),
165+
RunAsUser: util.NewType[int64](3001),
166+
},
167+
},
168+
}
169+
170+
for testName, testCase := range tests {
171+
t.Run(testName, func(t *testing.T) {
172+
actual := testCase.sc.NewSecurityContext(testCase.secured)
173+
assert.Equalf(t, testCase.want, actual, "NewSecurityContext(%v)", testCase.secured)
174+
})
175+
}
176+
}

0 commit comments

Comments
 (0)