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

Commit 1a45671

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 1a45671

File tree

5 files changed

+166
-1
lines changed

5 files changed

+166
-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: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
"regexp"
25+
"testing"
26+
"time"
27+
28+
_ "github.com/kinvolk/lokomotive/pkg/components/flatcar-linux-update-operator"
29+
testutil "github.com/kinvolk/lokomotive/test/components/util"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/util/wait"
32+
"k8s.io/client-go/kubernetes"
33+
)
34+
35+
const (
36+
retryInterval = time.Second * 5
37+
timeout = time.Minute * 5
38+
contextTimeout = 10
39+
)
40+
41+
type componentTestCase struct {
42+
namespace string
43+
}
44+
45+
func TestDisableAutomountServiceAccountToken(t *testing.T) {
46+
client := testutil.CreateKubeClient(t)
47+
ns, _ := client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
48+
49+
var componentTestCases []componentTestCase
50+
51+
for _, val := range ns.Items {
52+
if !isSpecialNamespace(val.Name) {
53+
componentTestCases = append(componentTestCases, componentTestCase{
54+
namespace: val.Name,
55+
})
56+
}
57+
}
58+
59+
for _, tc := range componentTestCases {
60+
tc := tc
61+
t.Run(tc.namespace, func(t *testing.T) {
62+
t.Parallel()
63+
64+
if err := wait.PollImmediate(
65+
retryInterval, timeout, checkDefaultServiceAccountPatch(client, tc),
66+
); err != nil {
67+
t.Fatalf("%v", err)
68+
}
69+
})
70+
}
71+
}
72+
73+
func checkDefaultServiceAccountPatch(client kubernetes.Interface, tc componentTestCase) wait.ConditionFunc {
74+
return func() (done bool, err error) {
75+
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout*time.Second)
76+
defer cancel()
77+
78+
sa, err := client.CoreV1().ServiceAccounts(tc.namespace).Get(ctx, "default", metav1.GetOptions{})
79+
if err != nil {
80+
return false, fmt.Errorf("error getting service account: %v", err)
81+
}
82+
83+
automountServiceAccountToken := *sa.AutomountServiceAccountToken
84+
85+
if automountServiceAccountToken != false {
86+
//nolint:lll
87+
return false, fmt.Errorf("service account for namespace %q was not patched. Expected %v got %v", tc.namespace, false, automountServiceAccountToken)
88+
}
89+
90+
return true, nil
91+
}
92+
}
93+
94+
func isSpecialNamespace(ns string) bool {
95+
if ns == "kube-system" || ns == "kube-public" || ns == "kube-node-lease" {
96+
return true
97+
}
98+
99+
// check for metadata-access-test by calico
100+
matched, _ := regexp.Match(`metadata-access-test-.*`, []byte(ns))
101+
if matched {
102+
return true
103+
}
104+
105+
return false
106+
}

0 commit comments

Comments
 (0)