Skip to content

Commit 0d3fed4

Browse files
Add jitter to Default token expiration (#260)
* Adding jitter based on percent to default token expiration to avoid thundering herd * Adding mockery configs and math/rand mocks * Adding Jitter and updating tests cases for token expiration jitter * Remove jitter from ISRAv1 code path * Fixing test data with randomized token expiration
1 parent 3fb048a commit 0d3fed4

File tree

12 files changed

+218
-9
lines changed

12 files changed

+218
-9
lines changed

.mockery.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
quiet: False
2+
disable-version-string: True
3+
with-expecter: True
4+
mockname: "{{.InterfaceName}}"
5+
filename: "{{.MockName}}.go"
6+
outpkg: pkg/mocks
7+
packages:
8+
math/rand:
9+
interfaces:
10+
Source64:

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ require (
4747
github.com/prometheus/client_model v0.2.0 // indirect
4848
github.com/prometheus/common v0.32.1 // indirect
4949
github.com/prometheus/procfs v0.7.3 // indirect
50+
github.com/stretchr/objx v0.5.2 // indirect
5051
github.com/x448/float16 v0.8.4 // indirect
5152
golang.org/x/crypto v0.28.0 // indirect
5253
golang.org/x/net v0.30.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
261261
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
262262
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
263263
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
264+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
265+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
264266
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
265267
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
266268
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"crypto/x509/pkix"
2222
goflag "flag"
2323
"fmt"
24+
"math/rand"
2425
"net/http"
2526
"os"
2627
"strings"
@@ -46,6 +47,7 @@ import (
4647
)
4748

4849
var webhookVersion = "v0.1.0"
50+
var random = rand.New(rand.NewSource(time.Now().UnixNano()))
4951

5052
func main() {
5153
port := flag.Int("port", 443, "Port to listen on")
@@ -208,6 +210,7 @@ func main() {
208210
}
209211

210212
mod := handler.NewModifier(
213+
random,
211214
handler.WithAnnotationDomain(*annotationPrefix),
212215
handler.WithMountPath(*mountPath),
213216
handler.WithServiceAccountCache(saCache),

pkg/constants.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ permissions and limitations under the License.
1515
package pkg
1616

1717
const (
18-
// Default token expiration in seconds if none is defined,
19-
// which is 24hrs as that is max for EKS
18+
// 24hrs as that is max for EKS
19+
MaxTokenExpiration = int64(86400)
20+
// Default token expiration in seconds if none is defined, 22hrs
2021
DefaultTokenExpiration = int64(86400)
22+
// Used for the minimum jitter value when using the default token expiration
23+
DefaultMinTokenExpiration = int64(79200)
2124
// 10mins is min for kube-apiserver
2225
MinTokenExpiration = int64(600)
2326

pkg/handler/handler.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"io/ioutil"
23+
"math/rand"
2324
"net/http"
2425
"path/filepath"
2526
"strconv"
@@ -85,12 +86,17 @@ func WithSALookupGraceTime(saLookupGraceTime time.Duration) ModifierOpt {
8586
}
8687

8788
// NewModifier returns a Modifier with default values
88-
func NewModifier(opts ...ModifierOpt) *Modifier {
89+
func NewModifier(random *rand.Rand, opts ...ModifierOpt) *Modifier {
90+
if random == nil {
91+
random = rand.New(rand.NewSource(time.Now().UnixNano()))
92+
}
93+
8994
mod := &Modifier{
9095
AnnotationDomain: "eks.amazonaws.com",
9196
MountPath: "/var/run/secrets/eks.amazonaws.com/serviceaccount",
9297
volName: "aws-iam-token",
9398
tokenName: "token",
99+
rand: *random,
94100
}
95101
for _, opt := range opts {
96102
opt(mod)
@@ -109,6 +115,7 @@ type Modifier struct {
109115
volName string
110116
tokenName string
111117
saLookupGraceTime time.Duration
118+
rand rand.Rand
112119
}
113120

114121
type patchOperation struct {
@@ -417,6 +424,7 @@ func (m *Modifier) buildPodPatchConfig(pod *corev1.Pod) *podPatchConfig {
417424
regionalSTS, tokenExpiration := m.Cache.GetCommonConfigurations(pod.Spec.ServiceAccountName, pod.Namespace)
418425
tokenExpiration, containersToSkip := m.parsePodAnnotations(pod, tokenExpiration)
419426

427+
tokenExpiration = m.addJitterToDefaultToken(tokenExpiration)
420428
webhookPodCount.WithLabelValues("container_credentials").Inc()
421429

422430
return &podPatchConfig{
@@ -479,6 +487,15 @@ func (m *Modifier) buildPodPatchConfig(pod *corev1.Pod) *podPatchConfig {
479487
return nil
480488
}
481489

490+
func (m *Modifier) addJitterToDefaultToken(tokenExpiration int64) int64 {
491+
if tokenExpiration == pkg.DefaultTokenExpiration {
492+
klog.V(0).Infof("Adding jitter to default token expiration")
493+
tokenExpiration = m.rand.Int63n(pkg.DefaultTokenExpiration-pkg.DefaultMinTokenExpiration+int64(1)) + pkg.DefaultMinTokenExpiration
494+
}
495+
496+
return tokenExpiration
497+
}
498+
482499
// MutatePod takes a AdmissionReview, mutates the pod, and returns an AdmissionResponse
483500
func (m *Modifier) MutatePod(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
484501
badRequest := &v1beta1.AdmissionResponse{

pkg/handler/handler_pod_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const (
6262
// buildModifierFromPod gets values to set up test case environments with as if
6363
// the values were set by service account annotation/flag before the test case.
6464
// Test cases are defined entirely by pod yamls.
65-
func buildModifierFromPod(pod *corev1.Pod) *Modifier {
65+
func buildModifierFromPod(pod *corev1.Pod, t *testing.T) *Modifier {
6666
var modifierOpts []ModifierOpt
6767

6868
if path, ok := pod.Annotations[handlerMountPathAnnotation]; ok {
@@ -76,7 +76,7 @@ func buildModifierFromPod(pod *corev1.Pod) *Modifier {
7676
modifierOpts = append(modifierOpts, WithServiceAccountCache(buildFakeCacheFromPod(pod)))
7777
modifierOpts = append(modifierOpts, WithContainerCredentialsConfig(buildFakeConfigFromPod(pod)))
7878

79-
return NewModifier(modifierOpts...)
79+
return NewModifier(getAlwaysZeroRand(t), modifierOpts...)
8080
}
8181

8282
func buildFakeCacheFromPod(pod *corev1.Pod) *cache.FakeServiceAccountCache {
@@ -157,7 +157,7 @@ func TestUpdatePodSpec(t *testing.T) {
157157
pod.Spec.ServiceAccountName = "default"
158158

159159
t.Run(fmt.Sprintf("Pod %s in file %s", pod.Name, path), func(t *testing.T) {
160-
modifier := buildModifierFromPod(pod)
160+
modifier := buildModifierFromPod(pod, t)
161161
patchConfig := modifier.buildPodPatchConfig(pod)
162162
patch, _ := modifier.getPodSpecPatch(pod, patchConfig)
163163
patchBytes, err := json.Marshal(patch)

pkg/handler/handler_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ import (
1919
"bytes"
2020
"encoding/json"
2121
"github.com/aws/amazon-eks-pod-identity-webhook/pkg/containercredentials"
22+
mocks "github.com/aws/amazon-eks-pod-identity-webhook/pkg/mocks/math/rand"
2223
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/mock"
2325
"io"
2426
"io/ioutil"
2527
"k8s.io/apimachinery/pkg/types"
28+
"math/rand"
2629
"net/http"
2730
"net/http/httptest"
2831
"reflect"
@@ -49,9 +52,11 @@ func TestMutatePod(t *testing.T) {
4952
}
5053

5154
modifier := NewModifier(
55+
getAlwaysZeroRand(t),
5256
WithServiceAccountCache(cache.NewFakeServiceAccountCache(testServiceAccount)),
5357
WithContainerCredentialsConfig(&containercredentials.FakeConfig{}),
5458
)
59+
5560
cases := []struct {
5661
caseName string
5762
input *v1beta1.AdmissionReview
@@ -105,6 +110,7 @@ func TestMutatePod(t *testing.T) {
105110

106111
func TestMutatePod_MutationNotNeeded(t *testing.T) {
107112
modifier := NewModifier(
113+
getAlwaysZeroRand(t),
108114
WithServiceAccountCache(cache.NewFakeServiceAccountCache()),
109115
WithContainerCredentialsConfig(&containercredentials.FakeConfig{}),
110116
)
@@ -180,6 +186,17 @@ func serializeAdmissionReview(t *testing.T, want *v1beta1.AdmissionReview) []byt
180186
return wantedBytes
181187
}
182188

189+
func getAlwaysZeroRand(t *testing.T) *rand.Rand {
190+
// Mock random and always return 0
191+
mockRandomSource := mocks.NewSource64(t)
192+
mockRandomSource.On("Int63", mock.Anything).Return(int64(0))
193+
194+
mockRand := rand.New(mockRandomSource)
195+
mockRand.Int63()
196+
197+
return mockRand
198+
}
199+
183200
func TestModifierHandler(t *testing.T) {
184201
testServiceAccount := &corev1.ServiceAccount{}
185202
testServiceAccount.Name = "default"
@@ -190,6 +207,7 @@ func TestModifierHandler(t *testing.T) {
190207
}
191208

192209
modifier := NewModifier(
210+
getAlwaysZeroRand(t),
193211
WithServiceAccountCache(cache.NewFakeServiceAccountCache(testServiceAccount)),
194212
WithContainerCredentialsConfig(&containercredentials.FakeConfig{}),
195213
)

pkg/handler/testdata/containercredentials/rawPodSkip.pod.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ metadata:
99
testing.eks.amazonaws.com/containercredentials/mountPath: "/con-creds-mount-path"
1010
testing.eks.amazonaws.com/containercredentials/volumeName: "con-creds-volume-name"
1111
testing.eks.amazonaws.com/containercredentials/tokenPath: "con-creds-token-path"
12-
testing.eks.amazonaws.com/expectedPatch: '[{"op":"add","path":"/spec/volumes","value":[{"name":"con-creds-volume-name","projected":{"sources":[{"serviceAccountToken":{"audience":"con-creds-aud","expirationSeconds":86400,"path":"con-creds-token-path"}}]}}]},{"op":"add","path":"/spec/containers","value":[{"name":"sidecar","image":"amazonlinux","resources":{}},{"name":"balajilovesoreos","image":"amazonlinux","env":[{"name":"AWS_CONTAINER_CREDENTIALS_FULL_URI","value":"con-creds-uri"},{"name":"AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE","value":"/con-creds-mount-path/con-creds-token-path"}],"resources":{},"volumeMounts":[{"name":"con-creds-volume-name","readOnly":true,"mountPath":"/con-creds-mount-path"}]}]}]'
12+
testing.eks.amazonaws.com/expectedPatch: '[{"op":"add","path":"/spec/volumes","value":[{"name":"con-creds-volume-name","projected":{"sources":[{"serviceAccountToken":{"audience":"con-creds-aud","expirationSeconds":79200,"path":"con-creds-token-path"}}]}}]},{"op":"add","path":"/spec/containers","value":[{"name":"sidecar","image":"amazonlinux","resources":{}},{"name":"balajilovesoreos","image":"amazonlinux","env":[{"name":"AWS_CONTAINER_CREDENTIALS_FULL_URI","value":"con-creds-uri"},{"name":"AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE","value":"/con-creds-mount-path/con-creds-token-path"}],"resources":{},"volumeMounts":[{"name":"con-creds-volume-name","readOnly":true,"mountPath":"/con-creds-mount-path"}]}]}]'
1313
# Pod Annotation
1414
eks.amazonaws.com/skip-containers: "sidecar"
1515
spec:

pkg/handler/testdata/containercredentials/rawPodWithVolume.pod.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ metadata:
1010
testing.eks.amazonaws.com/containercredentials/mountPath: "/con-creds-mount-path"
1111
testing.eks.amazonaws.com/containercredentials/volumeName: "con-creds-volume-name"
1212
testing.eks.amazonaws.com/containercredentials/tokenPath: "con-creds-token-path"
13-
testing.eks.amazonaws.com/expectedPatch: '[{"op":"add","path":"/spec/volumes/0","value":{"name":"con-creds-volume-name","projected":{"sources":[{"serviceAccountToken":{"audience":"con-creds-aud","expirationSeconds":86400,"path":"con-creds-token-path"}}]}}},{"op":"add","path":"/spec/containers","value":[{"name":"balajilovesoreos","image":"amazonlinux","env":[{"name":"AWS_CONTAINER_CREDENTIALS_FULL_URI","value":"con-creds-uri"},{"name":"AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE","value":"/con-creds-mount-path/con-creds-token-path"}],"resources":{},"volumeMounts":[{"name":"con-creds-volume-name","readOnly":true,"mountPath":"/con-creds-mount-path"}]}]}]'
13+
testing.eks.amazonaws.com/expectedPatch: '[{"op":"add","path":"/spec/volumes/0","value":{"name":"con-creds-volume-name","projected":{"sources":[{"serviceAccountToken":{"audience":"con-creds-aud","expirationSeconds":79200,"path":"con-creds-token-path"}}]}}},{"op":"add","path":"/spec/containers","value":[{"name":"balajilovesoreos","image":"amazonlinux","env":[{"name":"AWS_CONTAINER_CREDENTIALS_FULL_URI","value":"con-creds-uri"},{"name":"AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE","value":"/con-creds-mount-path/con-creds-token-path"}],"resources":{},"volumeMounts":[{"name":"con-creds-volume-name","readOnly":true,"mountPath":"/con-creds-mount-path"}]}]}]'
1414
spec:
1515
containers:
1616
- image: amazonlinux

0 commit comments

Comments
 (0)