Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor annotations parser #239

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions pkg/annotations.go → pkg/annotations/constants.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
/*
Copyright 2010 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Copyright 2010 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
A copy of the License is located at
Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
A copy of the License is located at

http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0

or in the "license" file accompanying this file. This file is distributed
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the specific language governing
permissions and limitations under the License.
or in the "license" file accompanying this file. This file is distributed
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the specific language governing
permissions and limitations under the License.
*/
package pkg
package annotations

const (
// The audience annotation
Expand Down
94 changes: 94 additions & 0 deletions pkg/annotations/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
A copy of the License is located at

http://www.apache.org/licenses/LICENSE-2.0

or in the "license" file accompanying this file. This file is distributed
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the specific language governing
permissions and limitations under the License.
*/

package annotations

import (
"encoding/csv"
"strconv"
"strings"

"github.com/aws/amazon-eks-pod-identity-webhook/pkg"
corev1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
)

type PodAnnotations struct {
tokenExpiration *int64
containersToSkip map[string]bool
}

func (a *PodAnnotations) GetContainersToSkip() map[string]bool {
return a.containersToSkip
}

func (a *PodAnnotations) GetTokenExpiration(fallback int64) int64 {
if a.tokenExpiration == nil {
return fallback
} else {
return *a.tokenExpiration
}
}

// parsePodAnnotations parses the pod annotations that can influence mutation:
// - tokenExpiration. Overrides the given service account annotation/flag-level
// setting.
// - containersToSkip. A Pod specific setting since certain containers within a
// specific pod might need to be opted-out of mutation
func ParsePodAnnotations(pod *corev1.Pod, annotationDomain string) *PodAnnotations {
return &PodAnnotations{
tokenExpiration: parseTokenExpiration(annotationDomain, pod),
containersToSkip: parseContainersToSkip(annotationDomain, pod),
}
}

// parseContainersToSkip returns the containers of a pod to skip mutating
func parseContainersToSkip(annotationDomain string, pod *corev1.Pod) map[string]bool {
skippedNames := map[string]bool{}
skipContainersKey := annotationDomain + "/" + SkipContainersAnnotation

value, ok := pod.Annotations[skipContainersKey]
if !ok {
return nil
}
r := csv.NewReader(strings.NewReader(value))
// error means we don't skip any
podNames, err := r.Read()
if err != nil {
klog.Infof("Could not parse skip containers annotation on pod %s/%s: %v", pod.Namespace, pod.Name, err)
return nil
}
for _, name := range podNames {
skippedNames[name] = true
}
return skippedNames
}

func parseTokenExpiration(annotationDomain string, pod *corev1.Pod) *int64 {
expirationKey := annotationDomain + "/" + TokenExpirationAnnotation
expirationStr, ok := pod.Annotations[expirationKey]
if !ok {
return nil
}

expiration, err := strconv.ParseInt(expirationStr, 10, 64)
if err != nil {
klog.V(4).Infof("Found invalid value for token expiration on the pod annotation: %s, falling back to the default: %v", expirationStr, err)
return nil
}

val := pkg.ValidateMinTokenExpiration(expiration)
return &val
}
99 changes: 99 additions & 0 deletions pkg/annotations/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
A copy of the License is located at

http://www.apache.org/licenses/LICENSE-2.0

or in the "license" file accompanying this file. This file is distributed
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the specific language governing
permissions and limitations under the License.
*/

package annotations

import (
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
)

func TestParsePodAnnotations(t *testing.T) {
podNoAnnotations := `
apiVersion: v1
kind: Pod
metadata:
name: balajilovesoreos`
testcases := []struct {
name string
pod string

expectedContainersToSkip map[string]bool

fallbackExpiration int64
expectedExpiration int64
}{
{
name: "sidecar-containers",
pod: `
apiVersion: v1
kind: Pod
metadata:
name: balajilovesoreos
annotations:
testing.eks.amazonaws.com/skip-containers: "sidecar,foo"
`,
expectedContainersToSkip: map[string]bool{"sidecar": true, "foo": true},
},
{
name: "token-expiration",
pod: `
apiVersion: v1
kind: Pod
metadata:
name: balajilovesoreos
annotations:
testing.eks.amazonaws.com/token-expiration: "1234"
`,
fallbackExpiration: 4567,
expectedExpiration: 1234,
},
{
name: "token-expiration fallback",
pod: podNoAnnotations,
fallbackExpiration: 4567,
expectedExpiration: 4567,
},
{
name: "token-expiration round up to min value",
pod: `
apiVersion: v1
kind: Pod
metadata:
name: balajilovesoreos
annotations:
testing.eks.amazonaws.com/token-expiration: "0"
`,
fallbackExpiration: 4567,
expectedExpiration: 600,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
var pod *corev1.Pod

err := yaml.Unmarshal([]byte(tc.pod), &pod)
assert.NoError(t, err)

actual := ParsePodAnnotations(pod, "testing.eks.amazonaws.com")
assert.Equal(t, tc.expectedContainersToSkip, actual.GetContainersToSkip())
assert.Equal(t, tc.expectedExpiration, actual.GetTokenExpiration(tc.fallbackExpiration))
})
}
}
9 changes: 5 additions & 4 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"sync"

"github.com/aws/amazon-eks-pod-identity-webhook/pkg"
"github.com/aws/amazon-eks-pod-identity-webhook/pkg/annotations"
"github.com/prometheus/client_golang/prometheus"
v1 "k8s.io/api/core/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
Expand Down Expand Up @@ -212,7 +213,7 @@ func (c *serviceAccountCache) ToJSON() string {
func (c *serviceAccountCache) addSA(sa *v1.ServiceAccount) {
entry := &Entry{}

arn, ok := sa.Annotations[c.annotationPrefix+"/"+pkg.RoleARNAnnotation]
arn, ok := sa.Annotations[c.annotationPrefix+"/"+annotations.RoleARNAnnotation]
if ok {
if !strings.Contains(arn, "arn:") && c.composeRoleArn.Enabled {
arn = fmt.Sprintf("arn:%s:iam::%s:role/%s", c.composeRoleArn.Partition, c.composeRoleArn.AccountID, arn)
Expand All @@ -228,12 +229,12 @@ func (c *serviceAccountCache) addSA(sa *v1.ServiceAccount) {
}

entry.Audience = c.defaultAudience
if audience, ok := sa.Annotations[c.annotationPrefix+"/"+pkg.AudienceAnnotation]; ok {
if audience, ok := sa.Annotations[c.annotationPrefix+"/"+annotations.AudienceAnnotation]; ok {
entry.Audience = audience
}

entry.UseRegionalSTS = c.defaultRegionalSTS
if useRegionalStr, ok := sa.Annotations[c.annotationPrefix+"/"+pkg.UseRegionalSTSAnnotation]; ok {
if useRegionalStr, ok := sa.Annotations[c.annotationPrefix+"/"+annotations.UseRegionalSTSAnnotation]; ok {
useRegional, err := strconv.ParseBool(useRegionalStr)
if err != nil {
klog.V(4).Infof("Ignoring service account %s/%s invalid value for disable-regional-sts annotation", sa.Namespace, sa.Name)
Expand All @@ -243,7 +244,7 @@ func (c *serviceAccountCache) addSA(sa *v1.ServiceAccount) {
}

entry.TokenExpiration = c.defaultTokenExpiration
if tokenExpirationStr, ok := sa.Annotations[c.annotationPrefix+"/"+pkg.TokenExpirationAnnotation]; ok {
if tokenExpirationStr, ok := sa.Annotations[c.annotationPrefix+"/"+annotations.TokenExpirationAnnotation]; ok {
if tokenExpiration, err := strconv.ParseInt(tokenExpirationStr, 10, 64); err != nil {
klog.V(4).Infof("Found invalid value for token expiration, using %d seconds as default: %v", entry.TokenExpiration, err)
} else {
Expand Down
59 changes: 8 additions & 51 deletions pkg/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@
package handler

import (
"encoding/csv"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/aws/amazon-eks-pod-identity-webhook/pkg/annotations"
"github.com/aws/amazon-eks-pod-identity-webhook/pkg/containercredentials"

"github.com/aws/amazon-eks-pod-identity-webhook/pkg"
Expand Down Expand Up @@ -143,25 +142,6 @@ func logContext(podName, podGenerateName, serviceAccountName, namespace string)
"Namespace=%s", name, serviceAccountName, namespace)
}

// getContainersToSkip returns the containers of a pod to skip mutating
func getContainersToSkip(annotationDomain string, pod *corev1.Pod) map[string]bool {
skippedNames := map[string]bool{}
skipContainersKey := annotationDomain + "/" + pkg.SkipContainersAnnotation
if value, ok := pod.Annotations[skipContainersKey]; ok {
r := csv.NewReader(strings.NewReader(value))
// error means we don't skip any
podNames, err := r.Read()
if err != nil {
klog.Infof("Could not parse skip containers annotation on pod %s/%s: %v", pod.Namespace, pod.Name, err)
return skippedNames
}
for _, name := range podNames {
skippedNames[name] = true
}
}
return skippedNames
}

Copy link
Contributor Author

@modulitos modulitos Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has been moved to pkg/annotations/parser.go (with some minor changes)

func (m *Modifier) addEnvToContainer(container *corev1.Container, tokenFilePath string, patchConfig *podPatchConfig) bool {
var (
webIdentityKeysDefined bool
Expand Down Expand Up @@ -277,29 +257,6 @@ func (m *Modifier) addEnvToContainer(container *corev1.Container, tokenFilePath
return changed
}

// parsePodAnnotations parses the pod annotations that can influence mutation:
// - tokenExpiration. Overrides the given service account annotation/flag-level
// setting.
// - containersToSkip. A Pod specific setting since certain containers within a
// specific pod might need to be opted-out of mutation
func (m *Modifier) parsePodAnnotations(pod *corev1.Pod, serviceAccountTokenExpiration int64) (int64, map[string]bool) {
// override serviceaccount annotation/flag token expiration with pod
// annotation if present
tokenExpiration := serviceAccountTokenExpiration
expirationKey := m.AnnotationDomain + "/" + pkg.TokenExpirationAnnotation
if expirationStr, ok := pod.Annotations[expirationKey]; ok {
if expiration, err := strconv.ParseInt(expirationStr, 10, 64); err != nil {
klog.V(4).Infof("Found invalid value for token expiration, using %d seconds as default: %v", serviceAccountTokenExpiration, err)
} else {
tokenExpiration = pkg.ValidateMinTokenExpiration(expiration)
}
}

containersToSkip := getContainersToSkip(m.AnnotationDomain, pod)

return tokenExpiration, containersToSkip
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has been moved to pkg/annotations/parser.go (with some minor changes)

// getPodSpecPatch gets the patch operation to be applied to the given Pod
func (m *Modifier) getPodSpecPatch(pod *corev1.Pod, patchConfig *podPatchConfig) ([]patchOperation, bool) {
tokenFilePath := filepath.Join(patchConfig.MountPath, patchConfig.TokenPath)
Expand Down Expand Up @@ -413,15 +370,16 @@ func (m *Modifier) getPodSpecPatch(pod *corev1.Pod, patchConfig *podPatchConfig)
func (m *Modifier) buildPodPatchConfig(pod *corev1.Pod) *podPatchConfig {
// Container credentials method takes precedence
containerCredentialsPatchConfig := m.ContainerCredentialsConfig.Get(pod.Namespace, pod.Spec.ServiceAccountName)

podAnnotations := annotations.ParsePodAnnotations(pod, m.AnnotationDomain)
if containerCredentialsPatchConfig != nil {
regionalSTS, tokenExpiration := m.Cache.GetCommonConfigurations(pod.Spec.ServiceAccountName, pod.Namespace)
tokenExpiration, containersToSkip := m.parsePodAnnotations(pod, tokenExpiration)
regionalSTS, tokenExpirationFallback := m.Cache.GetCommonConfigurations(pod.Spec.ServiceAccountName, pod.Namespace)

webhookPodCount.WithLabelValues("container_credentials").Inc()

return &podPatchConfig{
ContainersToSkip: containersToSkip,
TokenExpiration: tokenExpiration,
ContainersToSkip: podAnnotations.GetContainersToSkip(),
TokenExpiration: podAnnotations.GetTokenExpiration(tokenExpirationFallback),
UseRegionalSTS: regionalSTS,
Audience: containerCredentialsPatchConfig.Audience,
MountPath: containerCredentialsPatchConfig.MountPath,
Expand Down Expand Up @@ -452,13 +410,12 @@ func (m *Modifier) buildPodPatchConfig(pod *corev1.Pod) *podPatchConfig {
}
klog.V(5).Infof("Value of roleArn after after cache retrieval for service account %s: %s", request.CacheKey(), response.RoleARN)
if response.RoleARN != "" {
tokenExpiration, containersToSkip := m.parsePodAnnotations(pod, response.TokenExpiration)

webhookPodCount.WithLabelValues("sts_web_identity").Inc()

return &podPatchConfig{
ContainersToSkip: containersToSkip,
TokenExpiration: tokenExpiration,
ContainersToSkip: podAnnotations.GetContainersToSkip(),
TokenExpiration: podAnnotations.GetTokenExpiration(response.TokenExpiration),
UseRegionalSTS: response.UseRegionalSTS,
Audience: response.Audience,
MountPath: m.MountPath,
Expand Down
Loading
Loading