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

Commit 830fb3d

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 e41922b commit 830fb3d

File tree

5 files changed

+161
-1
lines changed

5 files changed

+161
-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.DisableAutomountServiceAccountToken("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.DisableAutomountServiceAccountToken(ns, kubeconfig); err != nil {
8893
return err
8994
}
9095

pkg/components/util/install.go

Lines changed: 42 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,46 @@ func ensureNamespaceExists(name string, kubeconfigPath string) error {
5961
return nil
6062
}
6163

64+
// DisableAutomountServiceAccountToken updates default Service Account to not mount it in pods by default.
65+
func DisableAutomountServiceAccountToken(ns, kubeconfigPath string) error {
66+
if ns == "" {
67+
return fmt.Errorf("namespace name can't be empty")
68+
}
69+
70+
kubeconfig, err := ioutil.ReadFile(kubeconfigPath) // #nosec G304
71+
if err != nil {
72+
return fmt.Errorf("reading kubeconfig file: %w", err)
73+
}
74+
75+
cs, err := k8sutil.NewClientset(kubeconfig)
76+
if err != nil {
77+
return fmt.Errorf("creating clientset: %w", err)
78+
}
79+
80+
payload := []patchUInt32Value{{
81+
Op: "add",
82+
Path: "/automountServiceAccountToken",
83+
Value: false,
84+
}}
85+
86+
payloadBytes, _ := json.Marshal(payload)
87+
88+
ctx := context.TODO()
89+
sa, _ := cs.CoreV1().ServiceAccounts(ns).Get(ctx, "default", metav1.GetOptions{})
90+
91+
automountServiceAccountToken := sa.AutomountServiceAccountToken
92+
93+
if automountServiceAccountToken == nil {
94+
//nolint:lll
95+
_, err = cs.CoreV1().ServiceAccounts(ns).Patch(ctx, "default", types.JSONPatchType, payloadBytes, metav1.PatchOptions{})
96+
if err != nil {
97+
return err
98+
}
99+
}
100+
101+
return nil
102+
}
103+
62104
// InstallComponent installs given component using given kubeconfig as a Helm release using a Helm client.
63105
func InstallComponent(c components.Component, kubeconfig string) error {
64106
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: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 aks aws aws_edge 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+
}
43+
44+
func TestDisableAutomountServiceAccountToken(t *testing.T) {
45+
client := testutil.CreateKubeClient(t)
46+
ns, _ := client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
47+
48+
items := ns.Items
49+
50+
var componentTestCases []componentTestCase
51+
52+
for _, val := range items {
53+
if !isSpecialNamespace(val.Name) {
54+
componentTestCases = append(componentTestCases, componentTestCase{
55+
namespace: val.Name,
56+
})
57+
}
58+
}
59+
60+
for _, tc := range componentTestCases {
61+
tc := tc
62+
t.Run(tc.namespace, func(t *testing.T) {
63+
t.Parallel()
64+
65+
if err := wait.PollImmediate(
66+
retryInterval, timeout, checkDefaultServiceAccountPatch(client, tc),
67+
); err != nil {
68+
t.Fatalf("%v", err)
69+
}
70+
})
71+
}
72+
}
73+
74+
func checkDefaultServiceAccountPatch(client kubernetes.Interface, tc componentTestCase) wait.ConditionFunc {
75+
return func() (done bool, err error) {
76+
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout*time.Second)
77+
defer cancel()
78+
79+
sa, err := client.CoreV1().ServiceAccounts(tc.namespace).Get(ctx, "default", metav1.GetOptions{})
80+
if err != nil {
81+
return false, fmt.Errorf("error getting service account: %v", err)
82+
}
83+
84+
automountServiceAccountToken := *sa.AutomountServiceAccountToken
85+
86+
if automountServiceAccountToken != false {
87+
//nolint:lll
88+
return false, fmt.Errorf("service account for namespace %q was not patched. Expected %v got %v", tc.namespace, false, automountServiceAccountToken)
89+
}
90+
91+
return true, nil
92+
}
93+
}
94+
95+
func isSpecialNamespace(ns string) bool {
96+
if ns == "kube-system" || ns == "kube-public" || ns == "kube-node-lease" {
97+
return true
98+
}
99+
100+
return false
101+
}

0 commit comments

Comments
 (0)