Skip to content
This repository was archived by the owner on Jun 29, 2022. It is now read-only.

Commit 1888ae5

Browse files
author
knrt10
committed
Don't mount default ServiceAccount in pods
- Add security to components and default namespace by preventing user to mount default ServiceAccount to their pods. - Add test for components. Fixes #669 Signed-off-by: knrt10 <kautilya@kinvolk.io>
1 parent 7225aa9 commit 1888ae5

File tree

5 files changed

+190
-1
lines changed

5 files changed

+190
-1
lines changed

cli/cmd/cluster-apply.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
log "github.com/sirupsen/logrus"
2323
"github.com/spf13/cobra"
2424

25+
"github.com/kinvolk/lokomotive/pkg/components/util"
2526
"github.com/kinvolk/lokomotive/pkg/install"
2627
"github.com/kinvolk/lokomotive/pkg/k8sutil"
2728
"github.com/kinvolk/lokomotive/pkg/lokomotive"
@@ -107,6 +108,10 @@ func runClusterApply(cmd *cobra.Command, args []string) {
107108
}
108109
}
109110

111+
if err := util.PatchServiceAccount("default", kubeconfigPath); err != nil {
112+
ctxLogger.Fatalf("Applying patch to default service account failed: %v", err)
113+
}
114+
110115
if skipComponents {
111116
return
112117
}

cli/cmd/component-apply.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,12 @@ func applyComponents(lokoConfig *config.Config, kubeconfig string, componentName
8484
return diags
8585
}
8686

87-
if err := util.InstallComponent(component, kubeconfig); err != nil {
87+
if err = util.InstallComponent(component, kubeconfig); err != nil {
88+
return err
89+
}
90+
91+
ns := component.Metadata().Namespace
92+
if err = util.PatchServiceAccount(ns, kubeconfig); err != nil {
8893
return err
8994
}
9095

pkg/components/util/install.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package util
1616

1717
import (
1818
"context"
19+
"encoding/json"
1920
"fmt"
2021
"io/ioutil"
2122

@@ -29,6 +30,7 @@ import (
2930

3031
"github.com/kinvolk/lokomotive/pkg/components"
3132
"github.com/kinvolk/lokomotive/pkg/k8sutil"
33+
types "k8s.io/apimachinery/pkg/types"
3234
)
3335

3436
func ensureNamespaceExists(name string, kubeconfigPath string) error {
@@ -59,6 +61,41 @@ func ensureNamespaceExists(name string, kubeconfigPath string) error {
5961
return nil
6062
}
6163

64+
// PatchServiceAccount updates default Service Account to not mount it in pods by default.
65+
func PatchServiceAccount(ns, kubeconfigPath string) error {
66+
kubeconfig, err := ioutil.ReadFile(kubeconfigPath) // #nosec G304
67+
68+
if err != nil {
69+
return fmt.Errorf("reading kubeconfig file: %w", err)
70+
}
71+
72+
cs, err := k8sutil.NewClientset(kubeconfig)
73+
if err != nil {
74+
return fmt.Errorf("creating clientset: %w", err)
75+
}
76+
77+
if ns == "" {
78+
return fmt.Errorf("namespace name can't be empty")
79+
}
80+
81+
payload := []patchUInt32Value{{
82+
Op: "add",
83+
Path: "/automountServiceAccountToken",
84+
Value: false,
85+
}}
86+
87+
payloadBytes, _ := json.Marshal(payload)
88+
89+
//nolint:lll
90+
_, err = cs.CoreV1().ServiceAccounts(ns).Patch(context.TODO(), "default", types.JSONPatchType, payloadBytes, metav1.PatchOptions{})
91+
92+
if err != nil && !errors.IsAlreadyExists(err) {
93+
return err
94+
}
95+
96+
return nil
97+
}
98+
6299
// InstallComponent installs given component using given kubeconfig as a Helm release using a Helm client.
63100
func InstallComponent(c components.Component, kubeconfig string) error {
64101
name := c.Metadata().Name

pkg/components/util/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,10 @@ func (n *NodeSelector) Render() (string, error) {
7878

7979
return string(b), nil
8080
}
81+
82+
// patchUInt32Value specifies a patch operation for a uint32.
83+
type patchUInt32Value struct {
84+
Op string `json:"op"`
85+
Path string `json:"path"`
86+
Value bool `json:"value"`
87+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2020 The Lokomotive Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// +build packet
16+
// +build e2e
17+
18+
//nolint
19+
package components
20+
21+
import (
22+
"context"
23+
"fmt"
24+
"testing"
25+
"time"
26+
27+
_ "github.com/kinvolk/lokomotive/pkg/components/flatcar-linux-update-operator"
28+
testutil "github.com/kinvolk/lokomotive/test/components/util"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/util/wait"
31+
"k8s.io/client-go/kubernetes"
32+
)
33+
34+
const (
35+
retryInterval = time.Second * 5
36+
timeout = time.Minute * 5
37+
contextTimeout = 10
38+
)
39+
40+
type componentTestCase struct {
41+
namespace string
42+
platforms []testutil.Platform
43+
}
44+
45+
func TestPatchServiceAccount(t *testing.T) {
46+
client := testutil.CreateKubeClient(t)
47+
componentTestCases := []componentTestCase{
48+
{
49+
namespace: "default",
50+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
51+
},
52+
{
53+
namespace: "cert-manager",
54+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
55+
},
56+
{
57+
namespace: "projectcontour",
58+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
59+
},
60+
{
61+
namespace: "dex",
62+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
63+
},
64+
{
65+
namespace: "external-dns",
66+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
67+
},
68+
{
69+
namespace: "reboot-coordinator",
70+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
71+
},
72+
{
73+
namespace: "gangway",
74+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
75+
},
76+
{
77+
namespace: "httpbin",
78+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
79+
},
80+
{
81+
namespace: "metallb-system",
82+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
83+
},
84+
{
85+
namespace: "openebs",
86+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
87+
},
88+
{
89+
namespace: "monitoring",
90+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
91+
},
92+
{
93+
namespace: "rook",
94+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformPacketARM},
95+
},
96+
}
97+
98+
for _, tc := range componentTestCases {
99+
tc := tc
100+
t.Run(tc.namespace, func(t *testing.T) {
101+
t.Parallel()
102+
103+
if !testutil.IsPlatformSupported(t, tc.platforms) {
104+
t.Skip()
105+
}
106+
107+
if err := wait.PollImmediate(
108+
retryInterval, timeout, checkDefaultServiceAccountPatch(client, tc),
109+
); err != nil {
110+
t.Fatalf("%v", err)
111+
}
112+
})
113+
}
114+
}
115+
116+
func checkDefaultServiceAccountPatch(client kubernetes.Interface, tc componentTestCase) wait.ConditionFunc {
117+
return func() (done bool, err error) {
118+
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout*time.Second)
119+
defer cancel()
120+
121+
sa, err := client.CoreV1().ServiceAccounts(tc.namespace).Get(ctx, "default", metav1.GetOptions{})
122+
if err != nil {
123+
return false, fmt.Errorf("error getting service account: %v", err)
124+
}
125+
126+
automountServiceAccountToken := *sa.AutomountServiceAccountToken
127+
128+
if automountServiceAccountToken != false {
129+
//nolint:lll
130+
return false, fmt.Errorf("Service account for %v namespace not patched. Expected %v got %v", tc.namespace, false, automountServiceAccountToken)
131+
}
132+
133+
return true, nil
134+
}
135+
}

0 commit comments

Comments
 (0)