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

Commit 4bf8ebd

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 4bf8ebd

File tree

5 files changed

+191
-1
lines changed

5 files changed

+191
-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: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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+
platforms []testutil.Platform
43+
}
44+
45+
func TestDisableAutomountServiceAccountToken(t *testing.T) {
46+
client := testutil.CreateKubeClient(t)
47+
componentTestCases := []componentTestCase{
48+
{
49+
namespace: "cert-manager",
50+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS, testutil.PlatformAKS},
51+
},
52+
{
53+
namespace: "projectcontour",
54+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS, testutil.PlatformAKS},
55+
},
56+
{
57+
namespace: "dex",
58+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS},
59+
},
60+
{
61+
namespace: "external-dns",
62+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS, testutil.PlatformAKS},
63+
},
64+
{
65+
namespace: "reboot-coordinator",
66+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS},
67+
},
68+
{
69+
namespace: "gangway",
70+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS},
71+
},
72+
{
73+
namespace: "httpbin",
74+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS, testutil.PlatformAKS},
75+
},
76+
{
77+
namespace: "metallb-system",
78+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge},
79+
},
80+
{
81+
namespace: "openebs",
82+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS},
83+
},
84+
{
85+
namespace: "monitoring",
86+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS},
87+
},
88+
{
89+
namespace: "rook",
90+
platforms: []testutil.Platform{testutil.PlatformPacket, testutil.PlatformAWSEdge, testutil.PlatformAWS},
91+
},
92+
}
93+
94+
for _, tc := range componentTestCases {
95+
tc := tc
96+
t.Run(tc.namespace, func(t *testing.T) {
97+
t.Parallel()
98+
99+
if !testutil.IsPlatformSupported(t, tc.platforms) {
100+
t.Skip()
101+
}
102+
103+
if err := wait.PollImmediate(
104+
retryInterval, timeout, checkDefaultServiceAccountPatch(client, tc),
105+
); err != nil {
106+
t.Fatalf("%v", err)
107+
}
108+
})
109+
}
110+
}
111+
112+
func checkDefaultServiceAccountPatch(client kubernetes.Interface, tc componentTestCase) wait.ConditionFunc {
113+
return func() (done bool, err error) {
114+
ctx, cancel := context.WithTimeout(context.Background(), contextTimeout*time.Second)
115+
defer cancel()
116+
117+
sa, err := client.CoreV1().ServiceAccounts(tc.namespace).Get(ctx, "default", metav1.GetOptions{})
118+
if err != nil {
119+
return false, fmt.Errorf("error getting service account: %v", err)
120+
}
121+
122+
automountServiceAccountToken := *sa.AutomountServiceAccountToken
123+
124+
if automountServiceAccountToken != false {
125+
//nolint:lll
126+
return false, fmt.Errorf("service account for namespace %q was not patched. Expected %v got %v", tc.namespace, false, automountServiceAccountToken)
127+
}
128+
129+
return true, nil
130+
}
131+
}

0 commit comments

Comments
 (0)