From 1e5db53740d0e83d484886d228608cbad1962ec4 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Tue, 21 Feb 2023 00:25:56 +0000 Subject: [PATCH 01/16] Adding Credential Package with source and helm chart --- credentialproviderpackage/Dockerfile | 14 + credentialproviderpackage/Makefile | 41 +++ .../credential-provider-package/.helmignore | 23 ++ .../credential-provider-package/Chart.yaml | 24 ++ .../templates/NOTES.txt | 1 + .../templates/_helpers.tpl | 88 +++++ .../templates/deployment.yaml | 89 +++++ .../templates/serviceaccount.yaml | 12 + .../credential-provider-package/values.yaml | 62 ++++ .../cmd/aws-credential-provider/main.go | 119 +++++++ credentialproviderpackage/go.mod | 28 ++ credentialproviderpackage/go.sum | 87 +++++ .../internal/test/files.go | 137 ++++++++ .../configurator/bottlerocket/bottlerocket.go | 171 ++++++++++ .../bottlerocket/bottlerocket_test.go | 323 ++++++++++++++++++ .../bottlerocket/testdata/testcreds | 3 + .../pkg/configurator/configurator.go | 17 + .../pkg/configurator/linux/linux.go | 223 ++++++++++++ .../pkg/configurator/linux/linux_test.go | 212 ++++++++++++ .../templates/credential-provider-config.yaml | 17 + .../linux/testdata/expected-config.yaml | 17 + .../pkg/configurator/linux/testdata/testcreds | 3 + .../pkg/constants/constants.go | 41 +++ .../pkg/filewriter/filewriter.go | 23 ++ .../pkg/filewriter/filewriter_defaults.go | 19 ++ .../pkg/filewriter/tmp_writer_test.go | 159 +++++++++ .../pkg/filewriter/writer.go | 99 ++++++ .../pkg/filewriter/writer_test.go | 203 +++++++++++ .../pkg/templater/partialyaml.go | 31 ++ .../pkg/templater/partialyaml_test.go | 131 +++++++ .../pkg/templater/templater.go | 66 ++++ .../pkg/templater/templater_test.go | 143 ++++++++ .../templater/testdata/invalid_template.yaml | 5 + .../pkg/templater/testdata/key4_template.yaml | 6 + .../testdata/partial_yaml_array_expected.yaml | 8 + .../testdata/partial_yaml_map_expected.yaml | 8 + .../partial_yaml_object_expected.yaml | 3 + .../test1_conditional_false_want.yaml | 2 + .../testdata/test1_conditional_true_want.yaml | 4 + .../templater/testdata/test1_template.yaml | 5 + .../testdata/test_indent_template.yaml | 5 + .../templater/testdata/test_indent_want.yaml | 4 + .../pkg/templater/yaml.go | 39 +++ credentialproviderpackage/pkg/utils/utils.go | 18 + credentialproviderpackage/skaffold.yaml | 24 ++ 45 files changed, 2757 insertions(+) create mode 100644 credentialproviderpackage/Dockerfile create mode 100644 credentialproviderpackage/Makefile create mode 100644 credentialproviderpackage/charts/credential-provider-package/.helmignore create mode 100644 credentialproviderpackage/charts/credential-provider-package/Chart.yaml create mode 100644 credentialproviderpackage/charts/credential-provider-package/templates/NOTES.txt create mode 100644 credentialproviderpackage/charts/credential-provider-package/templates/_helpers.tpl create mode 100644 credentialproviderpackage/charts/credential-provider-package/templates/deployment.yaml create mode 100644 credentialproviderpackage/charts/credential-provider-package/templates/serviceaccount.yaml create mode 100644 credentialproviderpackage/charts/credential-provider-package/values.yaml create mode 100644 credentialproviderpackage/cmd/aws-credential-provider/main.go create mode 100644 credentialproviderpackage/go.mod create mode 100644 credentialproviderpackage/go.sum create mode 100644 credentialproviderpackage/internal/test/files.go create mode 100644 credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go create mode 100644 credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go create mode 100644 credentialproviderpackage/pkg/configurator/bottlerocket/testdata/testcreds create mode 100644 credentialproviderpackage/pkg/configurator/configurator.go create mode 100644 credentialproviderpackage/pkg/configurator/linux/linux.go create mode 100644 credentialproviderpackage/pkg/configurator/linux/linux_test.go create mode 100644 credentialproviderpackage/pkg/configurator/linux/templates/credential-provider-config.yaml create mode 100644 credentialproviderpackage/pkg/configurator/linux/testdata/expected-config.yaml create mode 100644 credentialproviderpackage/pkg/configurator/linux/testdata/testcreds create mode 100644 credentialproviderpackage/pkg/constants/constants.go create mode 100644 credentialproviderpackage/pkg/filewriter/filewriter.go create mode 100644 credentialproviderpackage/pkg/filewriter/filewriter_defaults.go create mode 100644 credentialproviderpackage/pkg/filewriter/tmp_writer_test.go create mode 100644 credentialproviderpackage/pkg/filewriter/writer.go create mode 100644 credentialproviderpackage/pkg/filewriter/writer_test.go create mode 100644 credentialproviderpackage/pkg/templater/partialyaml.go create mode 100644 credentialproviderpackage/pkg/templater/partialyaml_test.go create mode 100644 credentialproviderpackage/pkg/templater/templater.go create mode 100644 credentialproviderpackage/pkg/templater/templater_test.go create mode 100644 credentialproviderpackage/pkg/templater/testdata/invalid_template.yaml create mode 100644 credentialproviderpackage/pkg/templater/testdata/key4_template.yaml create mode 100644 credentialproviderpackage/pkg/templater/testdata/partial_yaml_array_expected.yaml create mode 100644 credentialproviderpackage/pkg/templater/testdata/partial_yaml_map_expected.yaml create mode 100644 credentialproviderpackage/pkg/templater/testdata/partial_yaml_object_expected.yaml create mode 100644 credentialproviderpackage/pkg/templater/testdata/test1_conditional_false_want.yaml create mode 100644 credentialproviderpackage/pkg/templater/testdata/test1_conditional_true_want.yaml create mode 100644 credentialproviderpackage/pkg/templater/testdata/test1_template.yaml create mode 100644 credentialproviderpackage/pkg/templater/testdata/test_indent_template.yaml create mode 100644 credentialproviderpackage/pkg/templater/testdata/test_indent_want.yaml create mode 100644 credentialproviderpackage/pkg/templater/yaml.go create mode 100644 credentialproviderpackage/pkg/utils/utils.go create mode 100644 credentialproviderpackage/skaffold.yaml diff --git a/credentialproviderpackage/Dockerfile b/credentialproviderpackage/Dockerfile new file mode 100644 index 00000000..89262061 --- /dev/null +++ b/credentialproviderpackage/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.18-buster +ENV GOTRACEBACK=single +ENV GOPROXY=direct +WORKDIR /app +COPY go.mod . +COPY go.sum . +COPY cmd/ cmd/ +COPY ecr-credential-provider /eksa-binaries/ +COPY aws_signing_helper /eksa-binaries/ +COPY pkg/ pkg/ +ARG SKAFFOLD_GO_GCFLAGS +RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o app cmd/aws-credential-provider/*.go + +CMD ["/app/app"] \ No newline at end of file diff --git a/credentialproviderpackage/Makefile b/credentialproviderpackage/Makefile new file mode 100644 index 00000000..81b94688 --- /dev/null +++ b/credentialproviderpackage/Makefile @@ -0,0 +1,41 @@ +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +REPO_ROOT=$(shell git rev-parse --show-toplevel)/credentialproviderpackage +GOLANG_VERSION?="1.18" +#GO ?= $(shell source $(REPO_ROOT)/scripts/common.sh && build::common::get_go_path $(GOLANG_VERSION))/go +GO = go + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +all: build + +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +clean: ## Clean output directory, and the built binary + rm -rf output/ + rm -rf bin/* + rm cover.out + +##@ Build + +build: ## Build Binary + mkdir -p $(REPO_ROOT)/bin + $(GO) mod tidy -compat=$(GOLANG_VERSION) + $(GO) build -o $(REPO_ROOT)/bin/aws-credential-provider $(REPO_ROOT)/cmd/aws-credential-provider/*.go + +build-linux: + [ -d bin ] || mkdir bin + env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(MAKE) build + +run: + $(GO) run . + +test: build + $(GO) test ./... `$(GO) list $(GOTESTS) | grep -v mocks | grep -v fake | grep -v testutil` -coverprofile cover.out \ No newline at end of file diff --git a/credentialproviderpackage/charts/credential-provider-package/.helmignore b/credentialproviderpackage/charts/credential-provider-package/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/credentialproviderpackage/charts/credential-provider-package/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/credentialproviderpackage/charts/credential-provider-package/Chart.yaml b/credentialproviderpackage/charts/credential-provider-package/Chart.yaml new file mode 100644 index 00000000..499d9bf3 --- /dev/null +++ b/credentialproviderpackage/charts/credential-provider-package/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: credential-provider-package +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/NOTES.txt b/credentialproviderpackage/charts/credential-provider-package/templates/NOTES.txt new file mode 100644 index 00000000..f9339199 --- /dev/null +++ b/credentialproviderpackage/charts/credential-provider-package/templates/NOTES.txt @@ -0,0 +1 @@ +1. Installed Credential Provider Package diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/_helpers.tpl b/credentialproviderpackage/charts/credential-provider-package/templates/_helpers.tpl new file mode 100644 index 00000000..eb3e20b8 --- /dev/null +++ b/credentialproviderpackage/charts/credential-provider-package/templates/_helpers.tpl @@ -0,0 +1,88 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "credential-provider.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "credential-provider.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "credential-provider.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "credential-provider.labels" -}} +helm.sh/chart: {{ include "credential-provider.chart" . }} +{{ include "credential-provider.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "credential-provider.selectorLabels" -}} +app.kubernetes.io/name: {{ include "credential-provider.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "credential-provider.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "credential-provider.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create image name +*/}} +{{- define "template.image" -}} +{{- if eq (substr 0 7 .tag) "sha256:" -}} +{{- printf "/%s@%s" .repository .tag -}} +{{- else -}} +{{- printf "/%s:%s" .repository .tag -}} +{{- end -}} +{{- end -}} + +{{/* +Function to figure out os name +*/}} +{{- define "template.getOSName" -}} +{{- with first ((lookup "v1" "Node" "" "").items) -}} +{{- if contains "Bottlerocket" .status.nodeInfo.osImage -}} +{{- printf "bottlerocket" -}} +{{- else if contains "Amazon Linux" .status.nodeInfo.osImage -}} +{{- printf "docker" -}} +{{- else -}} +{{- printf "other" -}} +{{- end }} +{{- end }} +{{- end }} diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/deployment.yaml b/credentialproviderpackage/charts/credential-provider-package/templates/deployment.yaml new file mode 100644 index 00000000..bc98d2fa --- /dev/null +++ b/credentialproviderpackage/charts/credential-provider-package/templates/deployment.yaml @@ -0,0 +1,89 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "credential-provider.fullname" . }} + namespace: {{ .Release.Namespace | default .Values.defaultNamespace | quote }} + labels: + {{- include "credential-provider.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "credential-provider.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "credential-provider.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "credential-provider.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: credential-provider + image: {{ .Values.sourceRegistry }}/{{ .Values.image.repository }}@{{ .Values.image.digest }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + privileged: true + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: aws-creds + mountPath: /secrets/aws-creds + {{ $os := include "template.getOSName" .}} + {{- if eq $os "bottlerocket" }} + - mountPath: /run/api.sock + name: socket + {{- else}} + - mountPath: /node-files/kubelet-extra-args + name: kubelet-extra-args + - name: package-mounts + mountPath: /eksa-packages + {{- end}} + env: + - name: OS_TYPE + value: {{ template "template.getOSName" }} + volumes: + - name: aws-creds + secret: + secretName: {{.Values.secretName}} + optional: false + {{- if eq $os "bottlerocket" }} + - name: socket + hostPath: + path: /run/api.sock + {{- else if eq $os "docker"}} + - name: kubelet-extra-args + hostPath: + path: /etc/default/kubelet + type: FileOrCreate + {{- else}} + - name: kubelet-extra-args + hostPath: + path: /etc/sysconfig/kubelet + type: FileOrCreate + {{- end }} + {{- if ne $os "bottlerocket" }} + - name: package-mounts + hostPath: + path: /eksa-packages + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + hostPID: true diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/serviceaccount.yaml b/credentialproviderpackage/charts/credential-provider-package/templates/serviceaccount.yaml new file mode 100644 index 00000000..65ffbfdb --- /dev/null +++ b/credentialproviderpackage/charts/credential-provider-package/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "credential-provider.serviceAccountName" . }} + labels: + {{- include "credential-provider.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/credentialproviderpackage/charts/credential-provider-package/values.yaml b/credentialproviderpackage/charts/credential-provider-package/values.yaml new file mode 100644 index 00000000..78edfce9 --- /dev/null +++ b/credentialproviderpackage/charts/credential-provider-package/values.yaml @@ -0,0 +1,62 @@ +# Default values for credential-provider. +# This is a YAML-formatted file. + +replicaCount: 1 +# -- sourceRegistry for all container images in chart. +sourceRegistry: public.ecr.aws/eks-anywhere +defaultNamespace: eksa-packages + +image: + repository: "credential-provider-package" + tag: "{{credential-provider-package-tag}}" + digest: sha256:320e4bdbe1cf0ccc4a594c84acc339cf4d652c40d84b5313f79ae68b9493db85 + pullPolicy: IfNotPresent + +# application values +secretName: aws-cred +imagePatterns: "" +defaultCacheDuration: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/credentialproviderpackage/cmd/aws-credential-provider/main.go b/credentialproviderpackage/cmd/aws-credential-provider/main.go new file mode 100644 index 00000000..2545ddb4 --- /dev/null +++ b/credentialproviderpackage/cmd/aws-credential-provider/main.go @@ -0,0 +1,119 @@ +package main + +import ( + _ "embed" + "io/fs" + "log" + "os" + "strings" + "time" + + "github.com/fsnotify/fsnotify" + + cfg "credential-provider/pkg/configurator" + "credential-provider/pkg/configurator/bottlerocket" + "credential-provider/pkg/configurator/linux" + "credential-provider/pkg/constants" + "credential-provider/pkg/utils" +) + +func checkErrAndLog(err error, logger *log.Logger) { + if err != nil { + logger.Println(err) + os.Exit(0) + } +} + +func main() { + utils.InfoLogger.Println("Running at " + time.Now().UTC().String()) + + var configurator cfg.Configurator + osType := strings.ToLower(os.Getenv("OS_TYPE")) + if osType == "" { + utils.ErrorLogger.Println("Missing Environment Variable OS") + os.Exit(0) + } + config := createCredentialProviderConfigOptions() + if osType == constants.BottleRocket { + socket, err := os.Stat(constants.SocketPath) + checkErrAndLog(err, utils.ErrorLogger) + if socket.Mode().Type() == fs.ModeSocket { + configurator = bottlerocket.NewBottleRocketConfigurator() + configurator.Initialize(constants.SocketPath, config) + } else { + utils.ErrorLogger.Printf("Unexpected type %s expected socket\n", socket.Mode().Type()) + os.Exit(0) + } + } else { + configurator = linux.NewLinuxConfigurator() + configurator.Initialize("", config) + } + + err := configurator.UpdateAWSCredentials(constants.CredSrcPath, constants.Profile) + checkErrAndLog(err, utils.ErrorLogger) + utils.InfoLogger.Println("Aws credentials configured") + + err = configurator.UpdateCredentialProvider(constants.Profile) + checkErrAndLog(err, utils.ErrorLogger) + utils.InfoLogger.Println("Credential Provider Configured") + + err = configurator.CommitChanges() + checkErrAndLog(err, utils.ErrorLogger) + + utils.InfoLogger.Println("Kubelet Restarted") + + // Creating watcher for credentials + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + // Start listening for changes to the aws credentials + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Has(fsnotify.Create) { + if event.Name == constants.CredWatchData { + err = configurator.UpdateAWSCredentials(constants.CredSrcPath, constants.Profile) + checkErrAndLog(err, utils.ErrorLogger) + utils.InfoLogger.Println("Aws credentials successfully changed") + } + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("error:", err) + } + } + }() + + err = watcher.Add(constants.CredWatchPath) + if err != nil { + log.Fatal(err) + } + + // Block main goroutine forever. + <-make(chan struct{}) +} + +func createCredentialProviderConfigOptions() constants.CredentialProviderConfigOptions { + imagePatterns := os.Getenv("IMAGE_PATTERNS") + if imagePatterns == "" { + imagePatterns = constants.ImagePattern + } + defaultCacheDuration := os.Getenv("DEFAULT_CACHE_DURATION") + if defaultCacheDuration == "" { + defaultCacheDuration = constants.CacheDuration + } + + return constants.CredentialProviderConfigOptions{ + ImagePatterns: imagePatterns, + DefaultCacheDuration: defaultCacheDuration, + } +} diff --git a/credentialproviderpackage/go.mod b/credentialproviderpackage/go.mod new file mode 100644 index 00000000..98f1d575 --- /dev/null +++ b/credentialproviderpackage/go.mod @@ -0,0 +1,28 @@ +module credential-provider + +go 1.18 + +require ( + github.com/fsnotify/fsnotify v1.6.0 + github.com/google/go-cmp v0.5.9 + github.com/mitchellh/go-ps v1.0.0 + github.com/stretchr/testify v1.8.0 + k8s.io/apimachinery v0.26.0 + sigs.k8s.io/yaml v1.3.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.4.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect +) diff --git a/credentialproviderpackage/go.sum b/credentialproviderpackage/go.sum new file mode 100644 index 00000000..82b56f68 --- /dev/null +++ b/credentialproviderpackage/go.sum @@ -0,0 +1,87 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= +k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/credentialproviderpackage/internal/test/files.go b/credentialproviderpackage/internal/test/files.go new file mode 100644 index 00000000..d654aecc --- /dev/null +++ b/credentialproviderpackage/internal/test/files.go @@ -0,0 +1,137 @@ +package test + +import ( + "bytes" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "regexp" + "testing" + + "credential-provider/pkg/filewriter" +) + +var UpdateGoldenFiles = flag.Bool("update", false, "update golden files") + +func AssertFilesEquals(t *testing.T, gotPath, wantPath string) { + t.Helper() + gotFile := ReadFile(t, gotPath) + processUpdate(t, wantPath, gotFile) + wantFile := ReadFile(t, wantPath) + + if gotFile != wantFile { + cmd := exec.Command("diff", wantPath, gotPath) + result, err := cmd.Output() + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + if exitError.ExitCode() == 1 { + t.Fatalf("Results diff expected actual:\n%s", string(result)) + } + } + } + t.Fatalf("Files are different got =\n %s \n want =\n %s\n%s", gotFile, wantFile, err) + } +} + +func AssertContentToFile(t *testing.T, gotContent, wantFile string) { + t.Helper() + if wantFile == "" { + return + } + processUpdate(t, wantFile, gotContent) + + fileContent := ReadFile(t, wantFile) + if gotContent != fileContent { + diff, err := computeDiffBetweenContentAndFile([]byte(gotContent), wantFile) + if err != nil { + t.Fatalf("Content doesn't match file got =\n%s\n\n\nwant =\n%s\n", gotContent, fileContent) + } + if diff != "" { + t.Fatalf("Results diff expected actual for %s:\n%s", wantFile, string(diff)) + } + } +} + +func contentEqualToFile(gotContent []byte, wantFile string) (bool, error) { + if wantFile == "" && len(gotContent) == 0 { + return false, nil + } + + fileContent, err := ioutil.ReadFile(wantFile) + if err != nil { + return false, err + } + + return bytes.Equal(gotContent, fileContent), nil +} + +func computeDiffBetweenContentAndFile(content []byte, file string) (string, error) { + cmd := exec.Command("diff", "-u", file, "-") + cmd.Stdin = bytes.NewReader([]byte(content)) + result, err := cmd.Output() + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 1 { + return string(result), nil + } + + return "", fmt.Errorf("computing the difference between content and file %s: %v", file, err) + } + return "", nil +} + +func processUpdate(t *testing.T, filePath, content string) { + if *UpdateGoldenFiles { + if err := ioutil.WriteFile(filePath, []byte(content), 0o644); err != nil { + t.Fatalf("failed to update golden file %s: %v", filePath, err) + } + log.Printf("Golden file updated: %s", filePath) + } +} + +func ReadFileAsBytes(t *testing.T, file string) []byte { + bytesRead, err := ioutil.ReadFile(file) + if err != nil { + t.Fatalf("File [%s] reading error in test: %v", file, err) + } + + return bytesRead +} + +func ReadFile(t *testing.T, file string) string { + return string(ReadFileAsBytes(t, file)) +} + +func NewWriter(t *testing.T) (dir string, writer filewriter.FileWriter) { + dir, err := ioutil.TempDir(".", SanitizePath(t.Name())+"-") + if err != nil { + t.Fatalf("error setting up folder for test: %v", err) + } + + t.Cleanup(cleanupDir(t, dir)) + writer, err = filewriter.NewWriter(dir) + if err != nil { + t.Fatalf("error creating writer with folder for test: %v", err) + } + return dir, writer +} + +func cleanupDir(t *testing.T, dir string) func() { + return func() { + if !t.Failed() { + os.RemoveAll(dir) + } + } +} + +var sanitizePathChars = regexp.MustCompile(`[^\w-]`) + +const sanitizePathReplacementChar = "_" + +// SanitizePath sanitizes s so its usable as a path name. For safety, it assumes all characters that are not +// A-Z, a-z, 0-9, _ or - are illegal and replaces them with _. +func SanitizePath(s string) string { + return sanitizePathChars.ReplaceAllString(s, sanitizePathReplacementChar) +} diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go new file mode 100644 index 00000000..d39c0fb4 --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go @@ -0,0 +1,171 @@ +package bottlerocket + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + + "credential-provider/pkg/configurator" + "credential-provider/pkg/constants" +) + +type bottleRocket struct { + client http.Client + baseURL string + config constants.CredentialProviderConfigOptions +} + +type AwsCred struct { + Aws Aws `json:"aws"` +} +type Aws struct { + Config string `json:"config"` + Profile string `json:"profile"` + Region string `json:"region"` +} + +type brKubernetes struct { + Kubernetes kubernetes `json:"kubernetes"` +} +type ecrCredentialProvider struct { + CacheDuration string `json:"cache-duration"` + Enabled bool `json:"enabled"` + ImagePatterns []string `json:"image-patterns"` +} +type credentialProviders struct { + EcrCredentialProvider ecrCredentialProvider `json:"ecr-credential-provider"` +} +type kubernetes struct { + CredentialProviders credentialProviders `json:"credential-providers"` +} + +var _ configurator.Configurator = (*bottleRocket)(nil) + +func NewBottleRocketConfigurator() *bottleRocket { + return &bottleRocket{} +} + +func (b *bottleRocket) Initialize(socketPath string, config constants.CredentialProviderConfigOptions) { + b.baseURL = "http://localhost/" + b.config = config + b.client = http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", socketPath) + }, + }, + } +} + +func (b *bottleRocket) UpdateAWSCredentials(path string, profile string) error { + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + content := base64.StdEncoding.EncodeToString(data) + payload, err := createCredentialsPayload(content, profile) + if err != nil { + return err + } + err = b.sendSettingsSetRequest(payload) + if err != nil { + return err + } + + err = b.CommitChanges() + if err != nil { + return err + } + + return err +} + +func (b *bottleRocket) UpdateCredentialProvider(_ string) error { + payload, err := createCredentialProviderPayload(b.config) + if err != nil { + return err + } + err = b.sendSettingsSetRequest(payload) + if err != nil { + return err + } + + return err +} + +func (b *bottleRocket) CommitChanges() error { + // For Bottlerocket this step is committing all changes at once + commitPath := b.baseURL + "tx/commit_and_apply" + resp, err := b.client.Post(commitPath, "application/json", bytes.NewBuffer(make([]byte, 0))) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to commit changes: %s", resp.Status) + } + return nil +} + +func (b *bottleRocket) sendSettingsSetRequest(payload []byte) error { + settingsPath := b.baseURL + "settings" + req, err := http.NewRequest(http.MethodPatch, settingsPath, bytes.NewBuffer(payload)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + respPatch, err := b.client.Do(req) + if err != nil { + return err + } + defer respPatch.Body.Close() + + if respPatch.StatusCode != http.StatusNoContent { + return fmt.Errorf("failed patch request: %s", respPatch.Status) + } + + return nil + +} + +func createCredentialsPayload(content string, profile string) ([]byte, error) { + aws := Aws{ + Config: content, + Profile: profile, + } + + creds := AwsCred{Aws: aws} + + payload, err := json.Marshal(creds) + if err != nil { + return nil, err + } + return payload, nil +} + +func createCredentialProviderPayload(config constants.CredentialProviderConfigOptions) ([]byte, error) { + providerConfig := brKubernetes{ + Kubernetes: kubernetes{ + credentialProviders{ + ecrCredentialProvider{ + Enabled: true, + ImagePatterns: []string{config.ImagePatterns}, + CacheDuration: config.DefaultCacheDuration, + }, + }, + }, + } + + payload, err := json.Marshal(providerConfig) + if err != nil { + return nil, err + } + return payload, nil +} diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go new file mode 100644 index 00000000..f76e9511 --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go @@ -0,0 +1,323 @@ +package bottlerocket + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + + "credential-provider/pkg/constants" +) + +type response struct { + statusCode int + expectedBody []byte + responseMsg string +} + +func Test_bottleRocket_CommitChanges(t *testing.T) { + type fields struct { + client http.Client + baseURL string + config constants.CredentialProviderConfigOptions + } + + tests := []struct { + name string + fields fields + wantErr bool + response response + expected string + }{ + { + name: "test success", + fields: fields{ + client: http.Client{}, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + wantErr: false, + response: response{ + statusCode: http.StatusOK, + responseMsg: "", + }, + }, + { + name: "test fail", + fields: fields{ + client: http.Client{}, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + wantErr: true, + response: response{ + statusCode: http.StatusNotFound, + responseMsg: "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(tt.response.statusCode) + fmt.Fprintf(w, tt.response.responseMsg) + })) + b := &bottleRocket{ + client: tt.fields.client, + baseURL: svr.URL + "/", + config: tt.fields.config, + } + if err := b.CommitChanges(); (err != nil) != tt.wantErr { + t.Errorf("UpdateAWSCredentials() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_bottleRocket_UpdateAWSCredentials(t *testing.T) { + type fields struct { + client http.Client + baseURL string + config constants.CredentialProviderConfigOptions + } + type args struct { + path string + profile string + } + + tests := []struct { + name string + fields fields + args args + patchResponse response + commitResponse response + wantErr bool + }{ + { + name: "working credential update", + fields: fields{ + client: http.Client{}, + config: constants.CredentialProviderConfigOptions{}, + }, + args: args{ + path: "testdata/testcreds", + profile: "eksa-packages", + }, + patchResponse: response{ + statusCode: http.StatusNoContent, + expectedBody: []byte("{\"aws\":{\"config\":\"W3Byb2ZpbGUgZWtzYS1wYWNrYWdlc10KYXdzX2FjY2Vzc19rZXlfaWQ9QUtJQUlPU0ZPRE5ON0VYQU1QTEUKYXdzX3NlY3JldF9hY2Nlc3Nfa2V5PXdKYWxyWFV0bkZFTUkvSzdNREVORy9iUHhSZmlDWUVYQU1QTEVLRVk=\",\"profile\":\"eksa-packages\",\"region\":\"\"}}"), + responseMsg: "", + }, + commitResponse: response{ + statusCode: http.StatusOK, + responseMsg: "", + }, + wantErr: false, + }, + { + name: "commit credentials failed", + fields: fields{ + client: http.Client{}, + config: constants.CredentialProviderConfigOptions{}, + }, + args: args{ + path: "testdata/testcreds", + profile: "eksa-packages", + }, + patchResponse: response{ + statusCode: http.StatusNoContent, + expectedBody: []byte("{\"aws\":{\"config\":\"W3Byb2ZpbGUgZWtzYS1wYWNrYWdlc10KYXdzX2FjY2Vzc19rZXlfaWQ9QUtJQUlPU0ZPRE5ON0VYQU1QTEUKYXdzX3NlY3JldF9hY2Nlc3Nfa2V5PXdKYWxyWFV0bkZFTUkvSzdNREVORy9iUHhSZmlDWUVYQU1QTEVLRVk=\",\"profile\":\"eksa-packages\",\"region\":\"\"}}"), + responseMsg: "", + }, + commitResponse: response{ + statusCode: http.StatusNotFound, + responseMsg: "", + }, + wantErr: true, + }, + { + name: "failed to patch data", + fields: fields{ + client: http.Client{}, + config: constants.CredentialProviderConfigOptions{}, + }, + args: args{ + path: "testdata/testcreds", + profile: "eksa-packages", + }, + patchResponse: response{ + statusCode: http.StatusNotFound, + expectedBody: []byte("{\"aws\":{\"config\":\"W3Byb2ZpbGUgZWtzYS1wYWNrYWdlc10KYXdzX2FjY2Vzc19rZXlfaWQ9QUtJQUlPU0ZPRE5ON0VYQU1QTEUKYXdzX3NlY3JldF9hY2Nlc3Nfa2V5PXdKYWxyWFV0bkZFTUkvSzdNREVORy9iUHhSZmlDWUVYQU1QTEVLRVk=\",\"profile\":\"eksa-packages\",\"region\":\"\"}}"), + responseMsg: "", + }, + commitResponse: response{ + statusCode: http.StatusOK, + responseMsg: "", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPatch { + validatePatchRequest(w, r, t, tt.patchResponse) + } else if r.Method == http.MethodPost { + w.WriteHeader(tt.commitResponse.statusCode) + fmt.Fprintf(w, tt.commitResponse.responseMsg) + } else { + t.Errorf("Recieved unexected request %v", r.Method) + } + }), + ) + b := &bottleRocket{ + client: tt.fields.client, + baseURL: svr.URL + "/", + config: tt.fields.config, + } + if err := b.UpdateAWSCredentials(tt.args.path, tt.args.profile); (err != nil) != tt.wantErr { + t.Errorf("UpdateAWSCredentials() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_bottleRocket_UpdateCredentialProvider(t *testing.T) { + type fields struct { + client http.Client + baseURL string + config constants.CredentialProviderConfigOptions + } + + tests := []struct { + name string + fields fields + patchResponse response + wantErr bool + }{ + { + name: "default credential provider", + fields: fields{ + client: http.Client{}, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + patchResponse: response{ + statusCode: http.StatusNoContent, + expectedBody: []byte("{\"kubernetes\":{\"credential-providers\":{\"ecr-credential-provider\":{\"cache-duration\":\"30m\",\"enabled\":true,\"image-patterns\":[\"*.dkr.ecr.*.amazonaws.com\"]}}}}"), + responseMsg: "", + }, + wantErr: false, + }, + { + name: "non default values for credential provider", + fields: fields{ + client: http.Client{}, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: "123456789.dkr.ecr.test-region.amazonaws.com", + DefaultCacheDuration: "24h", + }, + }, + patchResponse: response{ + statusCode: http.StatusNoContent, + expectedBody: []byte("{\"kubernetes\":{\"credential-providers\":{\"ecr-credential-provider\":{\"cache-duration\":\"24h\",\"enabled\":true,\"image-patterns\":[\"123456789.dkr.ecr.test-region.amazonaws.com\"]}}}}"), + responseMsg: "", + }, + wantErr: false, + }, + { + name: "failed credential provider update", + fields: fields{ + client: http.Client{}, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + patchResponse: response{ + statusCode: http.StatusNotFound, + expectedBody: []byte("{\"kubernetes\":{\"credential-providers\":{\"ecr-credential-provider\":{\"cache-duration\":\"30m\",\"enabled\":true,\"image-patterns\":[\"*.dkr.ecr.*.amazonaws.com\"]}}}}"), + responseMsg: "", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPatch { + validatePatchRequest(w, r, t, tt.patchResponse) + } else { + t.Errorf("Recieved unexected request %v", r.Method) + } + }), + ) + + b := &bottleRocket{ + client: tt.fields.client, + baseURL: svr.URL + "/", + config: tt.fields.config, + } + if err := b.UpdateCredentialProvider(""); (err != nil) != tt.wantErr { + t.Errorf("UpdateCredentialProvider() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func validatePatchRequest(w http.ResponseWriter, r *http.Request, t *testing.T, patchResponse response) { + data, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("failed to read response") + } + if !bytes.Equal(data, patchResponse.expectedBody) { + t.Errorf("Patch message expcted %v got %v", patchResponse.expectedBody, data) + } + w.WriteHeader(patchResponse.statusCode) + fmt.Fprintf(w, patchResponse.responseMsg) +} + +func Test_bottleRocket_Initialize(t *testing.T) { + type args struct { + socketPath string + config constants.CredentialProviderConfigOptions + } + tests := []struct { + name string + baseUrl string + args args + }{ + { + name: "simple initialization", + baseUrl: "http://localhost/", + args: args{ + socketPath: "/test/path.sock", + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := NewBottleRocketConfigurator() + b.Initialize(tt.args.socketPath, tt.args.config) + assert.Equal(t, tt.baseUrl, b.baseURL) + assert.Equal(t, tt.args.config, b.config) + assert.NotNil(t, b.client) + }) + } +} diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/testdata/testcreds b/credentialproviderpackage/pkg/configurator/bottlerocket/testdata/testcreds new file mode 100644 index 00000000..cec03763 --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/testdata/testcreds @@ -0,0 +1,3 @@ +[profile eksa-packages] +aws_access_key_id=AKIAIOSFODNN7EXAMPLE +aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \ No newline at end of file diff --git a/credentialproviderpackage/pkg/configurator/configurator.go b/credentialproviderpackage/pkg/configurator/configurator.go new file mode 100644 index 00000000..04f160bf --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/configurator.go @@ -0,0 +1,17 @@ +package configurator + +import "credential-provider/pkg/constants" + +type Configurator interface { + // Initialize Handles node specific configuration depending on OS + Initialize(filepath string, config constants.CredentialProviderConfigOptions) + + // UpdateAWSCredentials Handles AWS Credential Setup + UpdateAWSCredentials(sourcePath string, profile string) error + + // UpdateCredentialProvider Handles Credential Provider Setup + UpdateCredentialProvider(profile string) error + + // CommitChanges Applies changes to Kubelet + CommitChanges() error +} diff --git a/credentialproviderpackage/pkg/configurator/linux/linux.go b/credentialproviderpackage/pkg/configurator/linux/linux.go new file mode 100644 index 00000000..9fef9088 --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/linux/linux.go @@ -0,0 +1,223 @@ +package linux + +import ( + _ "embed" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + "syscall" + + ps "github.com/mitchellh/go-ps" + + "credential-provider/pkg/configurator" + "credential-provider/pkg/constants" + "credential-provider/pkg/templater" + "credential-provider/pkg/utils" +) + +//go:embed templates/credential-provider-config.yaml +var credProviderTemplate string + +type linuxOS struct { + profile string + extraArgsPath string + basePath string + config constants.CredentialProviderConfigOptions +} + +var _ configurator.Configurator = (*linuxOS)(nil) + +func NewLinuxConfigurator() *linuxOS { + return &linuxOS{ + profile: "", + extraArgsPath: constants.MountedExtraArgs, + basePath: constants.BasePath, + } +} + +func (c *linuxOS) Initialize(_ string, config constants.CredentialProviderConfigOptions) { + c.config = config +} + +func (c *linuxOS) UpdateAWSCredentials(sourcePath string, profile string) error { + c.profile = profile + dstPath := c.basePath + constants.CredOutFile + + err := copyWithPermissons(sourcePath, dstPath, 0600) + return err +} + +func (c *linuxOS) UpdateCredentialProvider(_ string) error { + // Adding to KUBELET_EXTRA_ARGS in place + file, err := ioutil.ReadFile(c.extraArgsPath) + if err != nil { + return err + } + + lines := strings.Split(string(file), "\n") + found := false + for i, line := range lines { + if strings.HasPrefix(line, "KUBELET_EXTRA_ARGS") { + found = true + args := c.updateKubeletArguments(line) + + if args != "" { + lines[i] = line + args + "\n" + } + } + } + if !found { + line := "KUBELET_EXTRA_ARGS=" + args := c.updateKubeletArguments(line) + if args != "" { + line = line + args + } + lines = append(lines, line) + } + + out := strings.Join(lines, "\n") + err = ioutil.WriteFile(c.extraArgsPath, []byte(out), 0644) + if err != nil { + return err + } + return nil +} + +func (c *linuxOS) CommitChanges() error { + process, err := findKubeletProcess() + if err != nil { + return err + } + err = killProcess(process) + if err != nil { + return err + } + return nil +} + +func killProcess(process ps.Process) error { + err := syscall.Kill(process.Pid(), syscall.SIGHUP) + if err != nil { + return err + } + return nil +} + +func findKubeletProcess() (ps.Process, error) { + processList, err := ps.Processes() + if err != nil { + return nil, err + } + for x := range processList { + process := processList[x] + if process.Executable() == "kubelet" { + return process, nil + } + } + return nil, fmt.Errorf("cannot find Kubelet Process") +} + +func copyWithPermissons(srcpath, dstpath string, permission os.FileMode) (err error) { + r, err := os.Open(srcpath) + if err != nil { + return err + } + defer r.Close() // ok to ignore error: file was opened read-only. + + w, err := os.Create(dstpath) + if err != nil { + return err + } + + defer func() { + c := w.Close() + // Report the error from Close, if any. + // But do so only if there isn't already + // an outgoing error. + if c != nil && err == nil { + err = c + } + }() + + _, err = io.Copy(w, r) + if err != nil { + return err + } + err = os.Chmod(dstpath, permission) + return err +} + +func copyBinaries() (string, error) { + srcPath := constants.BinPath + constants.ECRCredProviderBinary + dstPath := constants.BasePath + constants.ECRCredProviderBinary + err := copyWithPermissons(srcPath, dstPath, 0744) + if err != nil { + return "", err + } + + err = os.Chmod(dstPath, 0744) + if err != nil { + return "", err + } + + srcPath = constants.BinPath + constants.IAMRolesSigningBinary + dstPath = constants.BasePath + constants.IAMRolesSigningBinary + err = copyWithPermissons(srcPath, dstPath, 0744) + if err != nil { + return "", err + } + + err = os.Chmod(dstPath, 0744) + if err != nil { + return "", err + } + return fmt.Sprintf(" --image-credential-provider-bin-dir=%s", constants.BasePath), nil +} + +func (c *linuxOS) createConfig() (string, error) { + values := map[string]interface{}{ + "profile": c.profile, + "config": constants.BasePath + constants.CredOutFile, + "home": constants.BasePath, + "imagePattern": c.config.ImagePatterns, + "cacheDuration": c.config.DefaultCacheDuration, + } + + dstPath := c.basePath + constants.CredProviderFile + + bytes, err := templater.Execute(credProviderTemplate, values) + if err != nil { + return "", nil + } + err = ioutil.WriteFile(dstPath, bytes, 0644) + if err != nil { + return "", err + } + return fmt.Sprintf(" --image-credential-provider-config=%s", dstPath), nil +} + +func (c *linuxOS) updateKubeletArguments(line string) string { + args := "" + if !strings.Contains(line, "KubeletCredentialProviders") { + args += " --feature-gates=KubeletCredentialProviders=true" + } + + if !strings.Contains(line, "image-credential-provider-config") { + val, err := c.createConfig() + if err != nil { + utils.ErrorLogger.Printf("Error creating configuration %v", err) + } + args += val + + if !strings.Contains(line, "image-credential-provider-bin-dir") { + val, err = copyBinaries() + if err != nil { + utils.ErrorLogger.Printf("Error coping binaries %v\n", err) + } + args += val + } + } + return args +} diff --git a/credentialproviderpackage/pkg/configurator/linux/linux_test.go b/credentialproviderpackage/pkg/configurator/linux/linux_test.go new file mode 100644 index 00000000..6430b012 --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/linux/linux_test.go @@ -0,0 +1,212 @@ +package linux + +import ( + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "credential-provider/internal/test" + "credential-provider/pkg/constants" +) + +func Test_linuxOS_updateKubeletArguments(t *testing.T) { + testDir, _ := test.NewWriter(t) + dir := testDir + "/" + type fields struct { + profile string + extraArgsPath string + basePath string + config constants.CredentialProviderConfigOptions + } + type args struct { + line string + } + tests := []struct { + name string + fields fields + args args + outputConfigPath string + configWantPath string + want string + }{ + { + name: "test empty string", + fields: fields{ + profile: "eksa-packages", + extraArgsPath: dir, + basePath: dir, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + args: args{line: ""}, + outputConfigPath: dir + "/" + constants.CredProviderFile, + configWantPath: "testdata/expected-config.yaml", + want: fmt.Sprintf(" --feature-gates=KubeletCredentialProviders=true "+ + "--image-credential-provider-config=%s%s", dir, constants.CredProviderFile), + }, + { + name: "skip credential provider if already provided", + fields: fields{ + profile: "eksa-packages", + extraArgsPath: dir, + basePath: dir, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + args: args{line: " --feature-gates=KubeletCredentialProviders=true"}, + outputConfigPath: dir + "/" + constants.CredProviderFile, + configWantPath: "testdata/expected-config.yaml", + want: fmt.Sprintf(" --image-credential-provider-config=%s%s", dir, constants.CredProviderFile), + }, + { + name: "skip both cred provider and feature gate if provided", + fields: fields{ + profile: "eksa-packages", + extraArgsPath: dir, + basePath: dir, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + args: args{line: " --feature-gates=KubeletCredentialProviders=false --image-credential-provider-config=blah"}, + outputConfigPath: dir + "/" + constants.CredProviderFile, + configWantPath: "", + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &linuxOS{ + profile: tt.fields.profile, + extraArgsPath: tt.fields.extraArgsPath, + basePath: tt.fields.basePath, + config: tt.fields.config, + } + if got := c.updateKubeletArguments(tt.args.line); got != tt.want { + t.Errorf("updateKubeletArguments() = %v, want %v", got, tt.want) + } + if tt.configWantPath != "" { + test.AssertFilesEquals(t, tt.outputConfigPath, tt.configWantPath) + } + + }) + } +} + +func Test_linuxOS_UpdateAWSCredentials(t *testing.T) { + testDir, _ := test.NewWriter(t) + dir := testDir + "/" + type fields struct { + profile string + extraArgsPath string + basePath string + config constants.CredentialProviderConfigOptions + } + type args struct { + sourcePath string + profile string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "simple credential move", + fields: fields{ + profile: "eksa-packages", + extraArgsPath: dir, + basePath: dir, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + args: args{ + sourcePath: "testdata/testcreds", + profile: "eksa-packages", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dstFile := tt.fields.basePath + constants.CredOutFile + c := &linuxOS{ + profile: tt.fields.profile, + extraArgsPath: tt.fields.extraArgsPath, + basePath: tt.fields.basePath, + config: tt.fields.config, + } + if err := c.UpdateAWSCredentials(tt.args.sourcePath, tt.args.profile); (err != nil) != tt.wantErr { + t.Errorf("UpdateAWSCredentials() error = %v, wantErr %v", err, tt.wantErr) + } + info, err := os.Stat(dstFile) + if err != nil { + t.Errorf("Failed to open destination file") + } + if info.Mode().Perm() != os.FileMode(0600) { + t.Errorf("Credential file not saved with correct permission") + } + + if err != nil { + t.Errorf("Failed to set file back to readable") + } + expectedCreds, err := ioutil.ReadFile(tt.args.sourcePath) + if err != nil { + t.Errorf("Failed to read source credential file") + } + + actualCreds, err := ioutil.ReadFile(dstFile) + if err != nil { + t.Errorf("Failed to read created credential file") + } + assert.Equal(t, expectedCreds, actualCreds) + }) + } +} + +func Test_linuxOS_Initialize(t *testing.T) { + type fields struct { + profile string + extraArgsPath string + basePath string + config constants.CredentialProviderConfigOptions + } + type args struct { + in0 string + config constants.CredentialProviderConfigOptions + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "simple initialization", + args: args{ + in0: "", + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: constants.ImagePattern, + DefaultCacheDuration: constants.CacheDuration, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := NewLinuxConfigurator() + c.Initialize(tt.args.in0, tt.args.config) + assert.Equal(t, c.config, tt.args.config) + }) + } +} diff --git a/credentialproviderpackage/pkg/configurator/linux/templates/credential-provider-config.yaml b/credentialproviderpackage/pkg/configurator/linux/templates/credential-provider-config.yaml new file mode 100644 index 00000000..4701c6f2 --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/linux/templates/credential-provider-config.yaml @@ -0,0 +1,17 @@ +apiVersion: kubelet.config.k8s.io/v1alpha1 +kind: CredentialProviderConfig +providers: + - name: ecr-credential-provider + matchImages: + - "{{.imagePattern}}" + defaultCacheDuration: "{{.cacheDuration}}" + apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1 + env: + - name: AWS_PROFILE + value: {{.profile}} + - name: AWS_CONFIG_FILE + value: {{.config}} + - name: PATH + value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/eksa-packages + - name: HOME + value: {{.home}} diff --git a/credentialproviderpackage/pkg/configurator/linux/testdata/expected-config.yaml b/credentialproviderpackage/pkg/configurator/linux/testdata/expected-config.yaml new file mode 100644 index 00000000..2c7ffcd1 --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/linux/testdata/expected-config.yaml @@ -0,0 +1,17 @@ +apiVersion: kubelet.config.k8s.io/v1alpha1 +kind: CredentialProviderConfig +providers: + - name: ecr-credential-provider + matchImages: + - "*.dkr.ecr.*.amazonaws.com" + defaultCacheDuration: "30m" + apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1 + env: + - name: AWS_PROFILE + value: eksa-packages + - name: AWS_CONFIG_FILE + value: /eksa-packages/aws-creds + - name: PATH + value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/eksa-packages + - name: HOME + value: /eksa-packages/ diff --git a/credentialproviderpackage/pkg/configurator/linux/testdata/testcreds b/credentialproviderpackage/pkg/configurator/linux/testdata/testcreds new file mode 100644 index 00000000..cec03763 --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/linux/testdata/testcreds @@ -0,0 +1,3 @@ +[profile eksa-packages] +aws_access_key_id=AKIAIOSFODNN7EXAMPLE +aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \ No newline at end of file diff --git a/credentialproviderpackage/pkg/constants/constants.go b/credentialproviderpackage/pkg/constants/constants.go new file mode 100644 index 00000000..ec272ad5 --- /dev/null +++ b/credentialproviderpackage/pkg/constants/constants.go @@ -0,0 +1,41 @@ +package constants + +const ( + // Credential Provider constants + ImagePattern = "*.dkr.ecr.*.amazonaws.com" + CacheDuration = "30m" + CredProviderFile = "credential-provider-config.yaml" + + // Aws Credentials + CredSrcPath = "/secrets/aws-creds/config" + Profile = "eksa-packages" + CredWatchData = "/secrets/aws-creds/..data" + CredWatchPath = "/secrets/aws-creds/" + + // BottleRocket + SocketPath = "/run/api.sock" + + // Linux + BinPath = "/eksa-binaries/" + BasePath = "/eksa-packages/" + CredOutFile = "aws-creds" + MountedExtraArgs = "/node-files/kubelet-extra-args" + + // Binaries + ECRCredProviderBinary = "ecr-credential-provider" + IAMRolesSigningBinary = "aws_signing_helper" +) + +type OSType string + +const ( + Docker OSType = "docker" + Ubuntu = "ubuntu" + Redhat = "redhat" + BottleRocket = "bottlerocket" +) + +type CredentialProviderConfigOptions struct { + ImagePatterns string + DefaultCacheDuration string +} diff --git a/credentialproviderpackage/pkg/filewriter/filewriter.go b/credentialproviderpackage/pkg/filewriter/filewriter.go new file mode 100644 index 00000000..dde7c8a8 --- /dev/null +++ b/credentialproviderpackage/pkg/filewriter/filewriter.go @@ -0,0 +1,23 @@ +package filewriter + +import ( + "io" + "os" +) + +type FileWriter interface { + Write(fileName string, content []byte, f ...FileOptionsFunc) (path string, err error) + WithDir(dir string) (FileWriter, error) + CleanUp() + CleanUpTemp() + Dir() string + TempDir() string + Create(name string, f ...FileOptionsFunc) (_ io.WriteCloser, path string, _ error) +} + +type FileOptions struct { + IsTemp bool + Permissions os.FileMode +} + +type FileOptionsFunc func(op *FileOptions) diff --git a/credentialproviderpackage/pkg/filewriter/filewriter_defaults.go b/credentialproviderpackage/pkg/filewriter/filewriter_defaults.go new file mode 100644 index 00000000..9288400d --- /dev/null +++ b/credentialproviderpackage/pkg/filewriter/filewriter_defaults.go @@ -0,0 +1,19 @@ +package filewriter + +import ( + "os" +) + +const DefaultTmpFolder = "generated" + +func defaultFileOptions() *FileOptions { + return &FileOptions{true, os.ModePerm} +} + +func Permission0600(op *FileOptions) { + op.Permissions = 0o600 +} + +func PersistentFile(op *FileOptions) { + op.IsTemp = false +} diff --git a/credentialproviderpackage/pkg/filewriter/tmp_writer_test.go b/credentialproviderpackage/pkg/filewriter/tmp_writer_test.go new file mode 100644 index 00000000..e5443749 --- /dev/null +++ b/credentialproviderpackage/pkg/filewriter/tmp_writer_test.go @@ -0,0 +1,159 @@ +package filewriter_test + +import ( + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + + "credential-provider/pkg/filewriter" +) + +func TestTmpWriterWriteValid(t *testing.T) { + folder := "tmp_folder" + folder2 := "tmp_folder_2" + err := os.MkdirAll(folder2, os.ModePerm) + if err != nil { + t.Fatalf("error setting up test: %v", err) + } + defer os.RemoveAll(folder) + defer os.RemoveAll(folder2) + + tests := []struct { + testName string + dir string + fileName string + content []byte + }{ + { + testName: "dir doesn't exist", + dir: folder, + fileName: "TestTmpWriterWriteValid-success.yaml", + content: []byte(` + fake content + blablab + `), + }, + { + testName: "dir exists", + dir: folder2, + fileName: "test", + content: []byte(` + fake content + blablab + `), + }, + { + testName: "empty file name", + dir: folder, + fileName: "test", + content: []byte(` + fake content + blablab + `), + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + tr, err := filewriter.NewWriter(tt.dir) + if err != nil { + t.Fatalf("failed creating tmpWriter error = %v", err) + } + + gotPath, err := tr.Write(tt.fileName, tt.content) + if err != nil { + t.Fatalf("tmpWriter.Write() error = %v", err) + } + + if !strings.HasPrefix(gotPath, tt.dir) { + t.Errorf("tmpWriter.Write() = %v, want to start with %v", gotPath, tt.dir) + } + + if !strings.HasSuffix(gotPath, tt.fileName) { + t.Errorf("tmpWriter.Write() = %v, want to end with %v", gotPath, tt.fileName) + } + + content, err := ioutil.ReadFile(gotPath) + if err != nil { + t.Fatalf("error reading written file: %v", err) + } + + if string(content) != string(tt.content) { + t.Errorf("Write file content = %v, want %v", content, tt.content) + } + }) + } +} + +func TestTmpWriterWithDir(t *testing.T) { + rootFolder := "folder_root" + subFolder := "subFolder" + defer os.RemoveAll(rootFolder) + + tr, err := filewriter.NewWriter(rootFolder) + if err != nil { + t.Fatalf("failed creating tmpWriter error = %v", err) + } + + tr, err = tr.WithDir(subFolder) + if err != nil { + t.Fatalf("failed creating tmpWriter with subdir error = %v", err) + } + + gotPath, err := tr.Write("file.txt", []byte("file content")) + if err != nil { + t.Fatalf("tmpWriter.Write() error = %v", err) + } + + wantPathPrefix := filepath.Join(rootFolder, subFolder) + if !strings.HasPrefix(gotPath, wantPathPrefix) { + t.Errorf("tmpWriter.Write() = %v, want to start with %v", gotPath, wantPathPrefix) + } +} + +func TestCreate(t *testing.T) { + dir := t.TempDir() + const fileName = "test.txt" + + // Hard code the "generated". Its an implementation detail but we can't refactor it right now. + expectedPath := path.Join(dir, "generated", fileName) + expectedContent := []byte("test content") + + fr, err := filewriter.NewWriter(dir) + if err != nil { + t.Fatal(err) + } + + fh, path, err := fr.Create(fileName) + if err != nil { + t.Fatal(err) + } + + // We need to validate 2 things: (1) are the paths returned correct; (2) if we write content + // to the returned io.WriteCloser, is it written to the path also returened from the function. + + if path != expectedPath { + t.Fatalf("Received: %v; Expected: %v", path, expectedPath) + } + + if _, err := fh.Write(expectedContent); err != nil { + t.Fatal(err) + } + + if err := fh.Close(); err != nil { + t.Fatal(err) + } + + content, err := os.ReadFile(expectedPath) + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(content, expectedContent) { + t.Fatalf("Received: %v; Expected: %v", content, expectedContent) + } +} diff --git a/credentialproviderpackage/pkg/filewriter/writer.go b/credentialproviderpackage/pkg/filewriter/writer.go new file mode 100644 index 00000000..fe6239da --- /dev/null +++ b/credentialproviderpackage/pkg/filewriter/writer.go @@ -0,0 +1,99 @@ +package filewriter + +import ( + "errors" + "fmt" + "io" + "io/fs" + "io/ioutil" + "os" + "path/filepath" +) + +type writer struct { + dir string + tempDir string +} + +func NewWriter(dir string) (FileWriter, error) { + newFolder := filepath.Join(dir, DefaultTmpFolder) + if _, err := os.Stat(newFolder); errors.Is(err, os.ErrNotExist) { + err := os.MkdirAll(newFolder, os.ModePerm) + if err != nil { + return nil, fmt.Errorf("creating directory [%s]: %v", dir, err) + } + } + return &writer{dir: dir, tempDir: newFolder}, nil +} + +func (w *writer) Write(fileName string, content []byte, opts ...FileOptionsFunc) (string, error) { + o := buildOptions(w, opts) + + filePath := filepath.Join(o.BasePath, fileName) + err := ioutil.WriteFile(filePath, content, o.Permissions) + if err != nil { + return "", fmt.Errorf("writing to file [%s]: %v", filePath, err) + } + + return filePath, nil +} + +func (w *writer) WithDir(dir string) (FileWriter, error) { + return NewWriter(filepath.Join(w.dir, dir)) +} + +func (w *writer) Dir() string { + return w.dir +} + +func (w *writer) TempDir() string { + return w.tempDir +} + +func (w *writer) CleanUp() { + _, err := os.Stat(w.dir) + if err == nil { + os.RemoveAll(w.dir) + } +} + +func (w *writer) CleanUpTemp() { + _, err := os.Stat(w.tempDir) + if err == nil { + os.RemoveAll(w.tempDir) + } +} + +// Create creates a file with the given name rooted at w's base directory. +func (w *writer) Create(name string, opts ...FileOptionsFunc) (_ io.WriteCloser, path string, _ error) { + o := buildOptions(w, opts) + + path = filepath.Join(o.BasePath, name) + fh, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, o.Permissions) + return fh, path, err +} + +type options struct { + BasePath string + Permissions fs.FileMode +} + +// buildOptions converts a set of FileOptionsFunc's to a single options struct. +func buildOptions(w *writer, opts []FileOptionsFunc) options { + op := defaultFileOptions() + for _, fn := range opts { + fn(op) + } + + var basePath string + if op.IsTemp { + basePath = w.tempDir + } else { + basePath = w.dir + } + + return options{ + BasePath: basePath, + Permissions: op.Permissions, + } +} diff --git a/credentialproviderpackage/pkg/filewriter/writer_test.go b/credentialproviderpackage/pkg/filewriter/writer_test.go new file mode 100644 index 00000000..1eb084d2 --- /dev/null +++ b/credentialproviderpackage/pkg/filewriter/writer_test.go @@ -0,0 +1,203 @@ +package filewriter_test + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "credential-provider/internal/test" + "credential-provider/pkg/filewriter" +) + +func TestWriterWriteValid(t *testing.T) { + folder := "tmp_folder" + folder2 := "tmp_folder_2" + err := os.MkdirAll(folder2, os.ModePerm) + if err != nil { + t.Fatalf("error setting up test: %v", err) + } + defer os.RemoveAll(folder) + defer os.RemoveAll(folder2) + + tests := []struct { + testName string + dir string + fileName string + content []byte + }{ + { + testName: "test 1", + dir: folder, + fileName: "TestWriterWriteValid-success.yaml", + content: []byte(` + fake content + blablab + `), + }, + { + testName: "test 2", + dir: folder2, + fileName: "TestWriterWriteValid-success.yaml", + content: []byte(` + fake content + blablab + `), + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + tr, err := filewriter.NewWriter(tt.dir) + if err != nil { + t.Fatalf("failed creating writer error = %v", err) + } + + gotPath, err := tr.Write(tt.fileName, tt.content) + if err != nil { + t.Fatalf("writer.Write() error = %v", err) + } + + wantPath := filepath.Join(tt.dir, filewriter.DefaultTmpFolder, tt.fileName) + if strings.Compare(gotPath, wantPath) != 0 { + t.Errorf("writer.Write() = %v, want %v", gotPath, wantPath) + } + + test.AssertFilesEquals(t, gotPath, wantPath) + }) + } +} + +func TestEmptyFileName(t *testing.T) { + folder := "tmp_folder" + defer os.RemoveAll(folder) + tr, err := filewriter.NewWriter(folder) + if err != nil { + t.Fatalf("failed creating writer error = %v", err) + } + _, err = tr.Write("", []byte("content")) + if err == nil { + t.Fatalf("writer.Write() error is nil") + } +} + +func TestWriterWithDir(t *testing.T) { + rootFolder := "folder_root" + subFolder := "subFolder" + defer os.RemoveAll(rootFolder) + + tr, err := filewriter.NewWriter(rootFolder) + if err != nil { + t.Fatalf("failed creating writer error = %v", err) + } + + tr, err = tr.WithDir(subFolder) + if err != nil { + t.Fatalf("failed creating writer with subdir error = %v", err) + } + + gotPath, err := tr.Write("file.txt", []byte("file content")) + if err != nil { + t.Fatalf("writer.Write() error = %v", err) + } + + wantPathPrefix := filepath.Join(rootFolder, subFolder) + if !strings.HasPrefix(gotPath, wantPathPrefix) { + t.Errorf("writer.Write() = %v, want to start with %v", gotPath, wantPathPrefix) + } +} + +func TestWriterWritePersistent(t *testing.T) { + folder := "tmp_folder_opt" + folder2 := "tmp_folder_2_opt" + err := os.MkdirAll(folder2, os.ModePerm) + if err != nil { + t.Fatalf("error setting up test: %v", err) + } + defer os.RemoveAll(folder) + defer os.RemoveAll(folder2) + + tests := []struct { + testName string + dir string + fileName string + content []byte + options []filewriter.FileOptionsFunc + }{ + { + testName: "Write persistent file", + dir: folder, + fileName: "TestWriterWriteValid-success.yaml", + content: []byte(` + fake content + blablab + `), + options: []filewriter.FileOptionsFunc{filewriter.PersistentFile}, + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + tr, err := filewriter.NewWriter(tt.dir) + if err != nil { + t.Fatalf("failed creating writer error = %v", err) + } + + gotPath, err := tr.Write(tt.fileName, tt.content, tt.options...) + if err != nil { + t.Fatalf("writer.Write() error = %v", err) + } + + wantPath := filepath.Join(tt.dir, tt.fileName) + if strings.Compare(gotPath, wantPath) != 0 { + t.Errorf("writer.Write() = %v, want %v", gotPath, wantPath) + } + + test.AssertFilesEquals(t, gotPath, wantPath) + }) + } +} + +func TestWriterDir(t *testing.T) { + rootFolder := "folder_root" + defer os.RemoveAll(rootFolder) + + tr, err := filewriter.NewWriter(rootFolder) + if err != nil { + t.Fatalf("failed creating writer error = %v", err) + } + + if strings.Compare(tr.Dir(), rootFolder) != 0 { + t.Errorf("writer.Dir() = %v, want %v", tr.Dir(), rootFolder) + } +} + +func TestWriterTempDir(t *testing.T) { + rootFolder := "folder_root" + tempFolder := fmt.Sprintf("%s/generated", rootFolder) + defer os.RemoveAll(rootFolder) + + tr, err := filewriter.NewWriter(rootFolder) + if err != nil { + t.Fatalf("failed creating writer error = %v", err) + } + + if strings.Compare(tr.TempDir(), tempFolder) != 0 { + t.Errorf("writer.TempDir() = %v, want %v", tr.TempDir(), tempFolder) + } +} + +func TestWriterCleanUpTempDir(t *testing.T) { + rootFolder := "folder_root" + defer os.RemoveAll(rootFolder) + + tr, err := filewriter.NewWriter(rootFolder) + if err != nil { + t.Fatalf("failed creating writer error = %v", err) + } + + tr.CleanUpTemp() + + if _, err := os.Stat(tr.TempDir()); err == nil { + t.Errorf("writer.CleanUp(), want err, got nil") + } +} diff --git a/credentialproviderpackage/pkg/templater/partialyaml.go b/credentialproviderpackage/pkg/templater/partialyaml.go new file mode 100644 index 00000000..44112b27 --- /dev/null +++ b/credentialproviderpackage/pkg/templater/partialyaml.go @@ -0,0 +1,31 @@ +package templater + +import ( + "reflect" + "strings" + + "sigs.k8s.io/yaml" +) + +type PartialYaml map[string]interface{} + +func (p PartialYaml) AddIfNotZero(k string, v interface{}) { + if !isZeroVal(v) { + p[k] = v + } +} + +func isZeroVal(x interface{}) bool { + return x == nil || reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface()) +} + +func (p PartialYaml) ToYaml() (string, error) { + b, err := yaml.Marshal(p) + if err != nil { + return "", err + } + s := string(b) + s = strings.TrimSuffix(s, "\n") + + return s, nil +} diff --git a/credentialproviderpackage/pkg/templater/partialyaml_test.go b/credentialproviderpackage/pkg/templater/partialyaml_test.go new file mode 100644 index 00000000..464ed87e --- /dev/null +++ b/credentialproviderpackage/pkg/templater/partialyaml_test.go @@ -0,0 +1,131 @@ +package templater_test + +import ( + "reflect" + "testing" + + "credential-provider/internal/test" + "credential-provider/pkg/templater" +) + +func TestPartialYamlAddIfNotZero(t *testing.T) { + tests := []struct { + testName string + p templater.PartialYaml + k string + v interface{} + wantAdded bool + wantV interface{} + }{ + { + testName: "add string", + p: templater.PartialYaml{}, + k: "key", + v: "value", + wantAdded: true, + wantV: "value", + }, + { + testName: "add nil", + p: templater.PartialYaml{}, + k: "key", + v: nil, + wantAdded: false, + wantV: nil, + }, + { + testName: "add empty string", + p: templater.PartialYaml{}, + k: "key", + v: "", + wantAdded: false, + wantV: nil, + }, + { + testName: "add present string", + p: templater.PartialYaml{ + "key": "value_old", + }, + k: "key", + v: "value_new", + wantAdded: true, + wantV: "value_new", + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + tt.p.AddIfNotZero(tt.k, tt.v) + + gotV, gotAdded := tt.p[tt.k] + if tt.wantAdded != gotAdded { + t.Errorf("PartialYaml.AddIfNotZero() wasAdded = %v, wantAdded %v", gotAdded, tt.wantAdded) + } + + if !reflect.DeepEqual(gotV, tt.wantV) { + t.Errorf("PartialYaml.AddIfNotZero() gotValue = %v, wantValue %v", gotV, tt.wantV) + } + }) + } +} + +func TestPartialYamlToYaml(t *testing.T) { + tests := []struct { + testName string + p templater.PartialYaml + wantFile string + wantErr bool + }{ + { + testName: "simple object", + p: templater.PartialYaml{ + "key1": "value 1", + "key2": 2, + "key3": "value3", + }, + wantFile: "testdata/partial_yaml_object_expected.yaml", + wantErr: false, + }, + { + testName: "map", + p: templater.PartialYaml{ + "key1": "value 1", + "key2": 2, + "key3": map[string]string{ + "key_nest1": "value nest", + "key_nest2": "value nest 2", + }, + "key4": map[string]interface{}{ + "key_nest1": "value nest", + "key_nest2": 22, + }, + }, + wantFile: "testdata/partial_yaml_map_expected.yaml", + wantErr: false, + }, + { + testName: "array", + p: templater.PartialYaml{ + "key1": "value 1", + "key2": 2, + "key3": []string{"value array 1", "value array 2"}, + "key4": []interface{}{ + map[string]interface{}{ + "key_in_nest_array": "value", + "key_in_nest_array_2": 22, + }, + }, + }, + wantFile: "testdata/partial_yaml_array_expected.yaml", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + got, err := tt.p.ToYaml() + if (err != nil) != tt.wantErr { + t.Fatalf("PartialYaml.ToYaml() error = %v, wantErr %v", err, tt.wantErr) + } + test.AssertContentToFile(t, got, tt.wantFile) + }) + } +} diff --git a/credentialproviderpackage/pkg/templater/templater.go b/credentialproviderpackage/pkg/templater/templater.go new file mode 100644 index 00000000..437adb63 --- /dev/null +++ b/credentialproviderpackage/pkg/templater/templater.go @@ -0,0 +1,66 @@ +package templater + +import ( + "bytes" + "fmt" + "strings" + "text/template" + + "credential-provider/pkg/filewriter" +) + +type Templater struct { + writer filewriter.FileWriter +} + +func New(writer filewriter.FileWriter) *Templater { + return &Templater{ + writer: writer, + } +} + +func (t *Templater) WriteToFile(templateContent string, data interface{}, fileName string, f ...filewriter.FileOptionsFunc) (filePath string, err error) { + bytes, err := Execute(templateContent, data) + if err != nil { + return "", err + } + writtenFilePath, err := t.writer.Write(fileName, bytes, f...) + if err != nil { + return "", fmt.Errorf("writing template file: %v", err) + } + + return writtenFilePath, nil +} + +func (t *Templater) WriteBytesToFile(content []byte, fileName string, f ...filewriter.FileOptionsFunc) (filePath string, err error) { + writtenFilePath, err := t.writer.Write(fileName, content, f...) + if err != nil { + return "", fmt.Errorf("writing template file: %v", err) + } + + return writtenFilePath, nil +} + +func Execute(templateContent string, data interface{}) ([]byte, error) { + temp := template.New("tmpl") + funcMap := map[string]interface{}{ + "indent": func(spaces int, v string) string { + pad := strings.Repeat(" ", spaces) + return pad + strings.Replace(v, "\n", "\n"+pad, -1) + }, + "stringsJoin": strings.Join, + } + temp = temp.Funcs(funcMap) + + temp, err := temp.Parse(templateContent) + if err != nil { + return nil, fmt.Errorf("parsing template: %v", err) + } + + var buf bytes.Buffer + err = temp.Execute(&buf, data) + if err != nil { + return nil, fmt.Errorf("substituting values for template: %v", err) + } + return buf.Bytes(), nil +} diff --git a/credentialproviderpackage/pkg/templater/templater_test.go b/credentialproviderpackage/pkg/templater/templater_test.go new file mode 100644 index 00000000..f6c74386 --- /dev/null +++ b/credentialproviderpackage/pkg/templater/templater_test.go @@ -0,0 +1,143 @@ +package templater_test + +import ( + "os" + "strings" + "testing" + + "credential-provider/internal/test" + "credential-provider/pkg/filewriter" + "credential-provider/pkg/templater" +) + +func TestTemplaterWriteToFileSuccess(t *testing.T) { + type dataStruct struct { + Key1, Key2, Key3, KeyAndValue3 string + Conditional bool + } + + tests := []struct { + testName string + templateFile string + data dataStruct + fileName string + wantFilePath string + wantErr bool + }{ + { + testName: "with conditional true", + templateFile: "testdata/test1_template.yaml", + data: dataStruct{ + Key1: "value_1", + Key2: "value_2", + Key3: "value_3", + Conditional: true, + }, + fileName: "file_tmp.yaml", + wantFilePath: "testdata/test1_conditional_true_want.yaml", + wantErr: false, + }, + { + testName: "with conditional false", + templateFile: "testdata/test1_template.yaml", + data: dataStruct{ + Key1: "value_1", + Key2: "value_2", + Key3: "value_3", + Conditional: false, + }, + fileName: "file_tmp.yaml", + wantFilePath: "testdata/test1_conditional_false_want.yaml", + wantErr: false, + }, + { + testName: "with indent", + templateFile: "testdata/test_indent_template.yaml", + data: dataStruct{ + Key1: "value_1", + Key2: "value_2", + KeyAndValue3: "key3: value_3", + Conditional: true, + }, + fileName: "file_tmp.yaml", + wantFilePath: "testdata/test_indent_want.yaml", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + _, writer := test.NewWriter(t) + tr := templater.New(writer) + templateContent := test.ReadFile(t, tt.templateFile) + gotFilePath, err := tr.WriteToFile(templateContent, tt.data, tt.fileName) + if (err != nil) != tt.wantErr { + t.Fatalf("Templater.WriteToFile() error = %v, wantErr %v", err, tt.wantErr) + } + + if !strings.HasSuffix(gotFilePath, tt.fileName) { + t.Errorf("Templater.WriteToFile() = %v, want to end with %v", gotFilePath, tt.fileName) + } + + test.AssertFilesEquals(t, gotFilePath, tt.wantFilePath) + }) + } +} + +func TestTemplaterWriteToFileError(t *testing.T) { + folder := "tmp_folder" + defer os.RemoveAll(folder) + + writer, err := filewriter.NewWriter(folder) + if err != nil { + t.Fatalf("failed creating writer error = #{err}") + } + + type dataStruct struct { + Key1, Key2, Key3 string + Conditional bool + } + + tests := []struct { + testName string + templateFile string + data dataStruct + fileName string + }{ + { + testName: "invalid template", + templateFile: "testdata/invalid_template.yaml", + data: dataStruct{ + Key1: "value_1", + Key2: "value_2", + Key3: "value_3", + Conditional: true, + }, + fileName: "file_tmp.yaml", + }, + { + testName: "data doesn't exist", + templateFile: "testdata/key4_template.yaml", + data: dataStruct{ + Key1: "value_1", + Key2: "value_2", + Key3: "value_3", + Conditional: false, + }, + fileName: "file_tmp.yaml", + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + tr := templater.New(writer) + templateContent := test.ReadFile(t, tt.templateFile) + gotFilePath, err := tr.WriteToFile(templateContent, tt.data, tt.fileName) + if err == nil { + t.Errorf("Templater.WriteToFile() error = nil") + } + + if gotFilePath != "" { + t.Errorf("Templater.WriteToFile() = %v, want nil", gotFilePath) + } + }) + } +} diff --git a/credentialproviderpackage/pkg/templater/testdata/invalid_template.yaml b/credentialproviderpackage/pkg/templater/testdata/invalid_template.yaml new file mode 100644 index 00000000..5dc5cbfc --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/invalid_template.yaml @@ -0,0 +1,5 @@ +key1: {{ .Key1 }} +key2: {{ .Key2 +{{ if .Conditional }} +key3: {{ .Key3 }} +{{ end }} \ No newline at end of file diff --git a/credentialproviderpackage/pkg/templater/testdata/key4_template.yaml b/credentialproviderpackage/pkg/templater/testdata/key4_template.yaml new file mode 100644 index 00000000..c65df82d --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/key4_template.yaml @@ -0,0 +1,6 @@ +key1: {{ .Key1 }} +key2: {{ .Key2 }} +{{ if .Conditional }} +key3: {{ .Key3 }} +{{ end }} +key4: {{ .Key4 }} \ No newline at end of file diff --git a/credentialproviderpackage/pkg/templater/testdata/partial_yaml_array_expected.yaml b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_array_expected.yaml new file mode 100644 index 00000000..d492ba49 --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_array_expected.yaml @@ -0,0 +1,8 @@ +key1: value 1 +key2: 2 +key3: +- value array 1 +- value array 2 +key4: +- key_in_nest_array: value + key_in_nest_array_2: 22 \ No newline at end of file diff --git a/credentialproviderpackage/pkg/templater/testdata/partial_yaml_map_expected.yaml b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_map_expected.yaml new file mode 100644 index 00000000..37c88490 --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_map_expected.yaml @@ -0,0 +1,8 @@ +key1: value 1 +key2: 2 +key3: + key_nest1: value nest + key_nest2: value nest 2 +key4: + key_nest1: value nest + key_nest2: 22 \ No newline at end of file diff --git a/credentialproviderpackage/pkg/templater/testdata/partial_yaml_object_expected.yaml b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_object_expected.yaml new file mode 100644 index 00000000..6edb56eb --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_object_expected.yaml @@ -0,0 +1,3 @@ +key1: value 1 +key2: 2 +key3: value3 \ No newline at end of file diff --git a/credentialproviderpackage/pkg/templater/testdata/test1_conditional_false_want.yaml b/credentialproviderpackage/pkg/templater/testdata/test1_conditional_false_want.yaml new file mode 100644 index 00000000..d6154a35 --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/test1_conditional_false_want.yaml @@ -0,0 +1,2 @@ +key1: value_1 +key2: value_2 diff --git a/credentialproviderpackage/pkg/templater/testdata/test1_conditional_true_want.yaml b/credentialproviderpackage/pkg/templater/testdata/test1_conditional_true_want.yaml new file mode 100644 index 00000000..f1cfcb93 --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/test1_conditional_true_want.yaml @@ -0,0 +1,4 @@ +key1: value_1 +key2: value_2 + +key3: value_3 diff --git a/credentialproviderpackage/pkg/templater/testdata/test1_template.yaml b/credentialproviderpackage/pkg/templater/testdata/test1_template.yaml new file mode 100644 index 00000000..113d3ca7 --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/test1_template.yaml @@ -0,0 +1,5 @@ +key1: {{ .Key1 }} +key2: {{ .Key2 }} +{{ if .Conditional }} +key3: {{ .Key3 }} +{{ end }} \ No newline at end of file diff --git a/credentialproviderpackage/pkg/templater/testdata/test_indent_template.yaml b/credentialproviderpackage/pkg/templater/testdata/test_indent_template.yaml new file mode 100644 index 00000000..4393f80b --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/test_indent_template.yaml @@ -0,0 +1,5 @@ +key1: {{ .Key1 }} +key2: {{ .Key2 }} +{{ if .Conditional }} +{{ .KeyAndValue3 | indent 2 }} +{{ end }} \ No newline at end of file diff --git a/credentialproviderpackage/pkg/templater/testdata/test_indent_want.yaml b/credentialproviderpackage/pkg/templater/testdata/test_indent_want.yaml new file mode 100644 index 00000000..6d4ebdbf --- /dev/null +++ b/credentialproviderpackage/pkg/templater/testdata/test_indent_want.yaml @@ -0,0 +1,4 @@ +key1: value_1 +key2: value_2 + + key3: value_3 diff --git a/credentialproviderpackage/pkg/templater/yaml.go b/credentialproviderpackage/pkg/templater/yaml.go new file mode 100644 index 00000000..42e2ee7f --- /dev/null +++ b/credentialproviderpackage/pkg/templater/yaml.go @@ -0,0 +1,39 @@ +package templater + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/yaml" +) + +const objectSeparator string = "\n---\n" + +func AppendYamlResources(resources ...[]byte) []byte { + separator := []byte(objectSeparator) + + size := 0 + for _, resource := range resources { + size += len(resource) + len(separator) + } + + b := make([]byte, 0, size) + for _, resource := range resources { + b = append(b, resource...) + b = append(b, separator...) + } + + return b +} + +func ObjectsToYaml(objs ...runtime.Object) ([]byte, error) { + r := [][]byte{} + for _, o := range objs { + b, err := yaml.Marshal(o) + if err != nil { + return nil, fmt.Errorf("failed to marshal object: %v", err) + } + r = append(r, b) + } + return AppendYamlResources(r...), nil +} diff --git a/credentialproviderpackage/pkg/utils/utils.go b/credentialproviderpackage/pkg/utils/utils.go new file mode 100644 index 00000000..125e4b55 --- /dev/null +++ b/credentialproviderpackage/pkg/utils/utils.go @@ -0,0 +1,18 @@ +package utils + +import ( + "log" + "os" +) + +var ( + InfoLogger *log.Logger + WarningLogger *log.Logger + ErrorLogger *log.Logger +) + +func init() { + InfoLogger = log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile) + WarningLogger = log.New(os.Stderr, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile) + ErrorLogger = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile) +} diff --git a/credentialproviderpackage/skaffold.yaml b/credentialproviderpackage/skaffold.yaml new file mode 100644 index 00000000..ee11c0d6 --- /dev/null +++ b/credentialproviderpackage/skaffold.yaml @@ -0,0 +1,24 @@ +apiVersion: skaffold/v3 +kind: Config +metadata: + name: credential-provider +build: + tagPolicy: + envTemplate: + template: "{{.EMPTY}}" + artifacts: + - image: credentialpackage + docker: + dockerfile: Dockerfile +manifests: + helm: + releases: + - name: credential-provider-helm + chartPath: charts/credential-provider-package + setValueTemplates: + image.registry: "{{.ECR_PUBLIC_REGISTRY}}" + image.repository: "credentialpackage" + image.tag: "{{.IMAGE_DIGEST_credentialpackage}}" + image.digest: "{{.IMAGE_DIGEST_credentialpackage}}" + sourceRegistry: "{{.SKAFFOLD_DEFAULT_REPO}}" + From 084346c2fafde134f1a14452537317a7d2284b7d Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Wed, 22 Feb 2023 14:59:19 +0000 Subject: [PATCH 02/16] Adding new line to end of Dockerfile and Makefile --- credentialproviderpackage/Dockerfile | 2 +- credentialproviderpackage/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/credentialproviderpackage/Dockerfile b/credentialproviderpackage/Dockerfile index 89262061..4660509b 100644 --- a/credentialproviderpackage/Dockerfile +++ b/credentialproviderpackage/Dockerfile @@ -11,4 +11,4 @@ COPY pkg/ pkg/ ARG SKAFFOLD_GO_GCFLAGS RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o app cmd/aws-credential-provider/*.go -CMD ["/app/app"] \ No newline at end of file +CMD ["/app/app"] diff --git a/credentialproviderpackage/Makefile b/credentialproviderpackage/Makefile index 81b94688..a87e28d2 100644 --- a/credentialproviderpackage/Makefile +++ b/credentialproviderpackage/Makefile @@ -38,4 +38,4 @@ run: $(GO) run . test: build - $(GO) test ./... `$(GO) list $(GOTESTS) | grep -v mocks | grep -v fake | grep -v testutil` -coverprofile cover.out \ No newline at end of file + $(GO) test ./... `$(GO) list $(GOTESTS) | grep -v mocks | grep -v fake | grep -v testutil` -coverprofile cover.out From c6b3a91a49dcfdaf4ff7e800cbd4c411fe08e1f9 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Thu, 23 Feb 2023 00:34:16 +0000 Subject: [PATCH 03/16] Changed matchImages to take array instead of one repository. Change Initialize interface to remove socketpath and add that to bottlerocket constructor, Removed Notes from chart, moved from Deployment to Daemonset --- .../templates/NOTES.txt | 1 - .../{deployment.yaml => daemonset.yaml} | 8 +++- .../credential-provider-package/values.yaml | 9 ++-- .../cmd/aws-credential-provider/main.go | 22 +++++----- .../configurator/bottlerocket/bottlerocket.go | 23 +++++----- .../bottlerocket/bottlerocket_test.go | 42 +++++++++++++------ .../pkg/configurator/configurator.go | 2 +- .../pkg/configurator/linux/linux.go | 2 +- .../pkg/configurator/linux/linux_test.go | 42 +++++++++++++------ .../templates/credential-provider-config.yaml | 4 +- .../expected-config-multiple-patterns.yaml | 18 ++++++++ .../pkg/constants/constants.go | 8 ++-- 12 files changed, 119 insertions(+), 62 deletions(-) delete mode 100644 credentialproviderpackage/charts/credential-provider-package/templates/NOTES.txt rename credentialproviderpackage/charts/credential-provider-package/templates/{deployment.yaml => daemonset.yaml} (90%) create mode 100644 credentialproviderpackage/pkg/configurator/linux/testdata/expected-config-multiple-patterns.yaml diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/NOTES.txt b/credentialproviderpackage/charts/credential-provider-package/templates/NOTES.txt deleted file mode 100644 index f9339199..00000000 --- a/credentialproviderpackage/charts/credential-provider-package/templates/NOTES.txt +++ /dev/null @@ -1 +0,0 @@ -1. Installed Credential Provider Package diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/deployment.yaml b/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml similarity index 90% rename from credentialproviderpackage/charts/credential-provider-package/templates/deployment.yaml rename to credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml index bc98d2fa..cf082464 100644 --- a/credentialproviderpackage/charts/credential-provider-package/templates/deployment.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml @@ -1,5 +1,5 @@ apiVersion: apps/v1 -kind: Deployment +kind: DaemonSet metadata: name: {{ include "credential-provider.fullname" . }} namespace: {{ .Release.Namespace | default .Values.defaultNamespace | quote }} @@ -49,10 +49,14 @@ spec: env: - name: OS_TYPE value: {{ template "template.getOSName" }} + - name: MATCH_IMAGES + value: '{{ join "," .Values.application.matchImages }}' + - name: DEFAULT_CACHE_DURATION + value: {{.Values.application.defaultCacheDuration}} volumes: - name: aws-creds secret: - secretName: {{.Values.secretName}} + secretName: {{.Values.application.secretName}} optional: false {{- if eq $os "bottlerocket" }} - name: socket diff --git a/credentialproviderpackage/charts/credential-provider-package/values.yaml b/credentialproviderpackage/charts/credential-provider-package/values.yaml index 78edfce9..ee2de9da 100644 --- a/credentialproviderpackage/charts/credential-provider-package/values.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/values.yaml @@ -9,13 +9,14 @@ defaultNamespace: eksa-packages image: repository: "credential-provider-package" tag: "{{credential-provider-package-tag}}" - digest: sha256:320e4bdbe1cf0ccc4a594c84acc339cf4d652c40d84b5313f79ae68b9493db85 + digest: "{{credential-provider-package-digest}}" pullPolicy: IfNotPresent # application values -secretName: aws-cred -imagePatterns: "" -defaultCacheDuration: "" +application: + secretName: aws-cred + matchImages: [] + defaultCacheDuration: "" imagePullSecrets: [] nameOverride: "" diff --git a/credentialproviderpackage/cmd/aws-credential-provider/main.go b/credentialproviderpackage/cmd/aws-credential-provider/main.go index 2545ddb4..a8c62331 100644 --- a/credentialproviderpackage/cmd/aws-credential-provider/main.go +++ b/credentialproviderpackage/cmd/aws-credential-provider/main.go @@ -20,7 +20,7 @@ import ( func checkErrAndLog(err error, logger *log.Logger) { if err != nil { logger.Println(err) - os.Exit(0) + os.Exit(1) } } @@ -31,24 +31,24 @@ func main() { osType := strings.ToLower(os.Getenv("OS_TYPE")) if osType == "" { utils.ErrorLogger.Println("Missing Environment Variable OS") - os.Exit(0) + os.Exit(1) } config := createCredentialProviderConfigOptions() if osType == constants.BottleRocket { socket, err := os.Stat(constants.SocketPath) checkErrAndLog(err, utils.ErrorLogger) if socket.Mode().Type() == fs.ModeSocket { - configurator = bottlerocket.NewBottleRocketConfigurator() - configurator.Initialize(constants.SocketPath, config) + configurator = bottlerocket.NewBottleRocketConfigurator(constants.SocketPath) + } else { utils.ErrorLogger.Printf("Unexpected type %s expected socket\n", socket.Mode().Type()) - os.Exit(0) + os.Exit(1) } } else { configurator = linux.NewLinuxConfigurator() - configurator.Initialize("", config) } + configurator.Initialize(config) err := configurator.UpdateAWSCredentials(constants.CredSrcPath, constants.Profile) checkErrAndLog(err, utils.ErrorLogger) utils.InfoLogger.Println("Aws credentials configured") @@ -103,13 +103,15 @@ func main() { } func createCredentialProviderConfigOptions() constants.CredentialProviderConfigOptions { - imagePatterns := os.Getenv("IMAGE_PATTERNS") - if imagePatterns == "" { - imagePatterns = constants.ImagePattern + imagePatternsValues := os.Getenv("MATCH_IMAGES") + if imagePatternsValues == "" { + imagePatternsValues = constants.DefaultImagePattern } + imagePatterns := strings.Split(imagePatternsValues, ",") + defaultCacheDuration := os.Getenv("DEFAULT_CACHE_DURATION") if defaultCacheDuration == "" { - defaultCacheDuration = constants.CacheDuration + defaultCacheDuration = constants.DefaultCacheDuration } return constants.CredentialProviderConfigOptions{ diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go index d39c0fb4..aac462db 100644 --- a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go @@ -46,20 +46,21 @@ type kubernetes struct { var _ configurator.Configurator = (*bottleRocket)(nil) -func NewBottleRocketConfigurator() *bottleRocket { - return &bottleRocket{} +func NewBottleRocketConfigurator(socketPath string) *bottleRocket { + return &bottleRocket{ + client: http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", socketPath) + }, + }, + }, + } } -func (b *bottleRocket) Initialize(socketPath string, config constants.CredentialProviderConfigOptions) { +func (b *bottleRocket) Initialize(config constants.CredentialProviderConfigOptions) { b.baseURL = "http://localhost/" b.config = config - b.client = http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socketPath) - }, - }, - } } func (b *bottleRocket) UpdateAWSCredentials(path string, profile string) error { @@ -156,7 +157,7 @@ func createCredentialProviderPayload(config constants.CredentialProviderConfigOp credentialProviders{ ecrCredentialProvider{ Enabled: true, - ImagePatterns: []string{config.ImagePatterns}, + ImagePatterns: config.ImagePatterns, CacheDuration: config.DefaultCacheDuration, }, }, diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go index f76e9511..52572089 100644 --- a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go @@ -38,8 +38,8 @@ func Test_bottleRocket_CommitChanges(t *testing.T) { fields: fields{ client: http.Client{}, config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, wantErr: false, @@ -53,8 +53,8 @@ func Test_bottleRocket_CommitChanges(t *testing.T) { fields: fields{ client: http.Client{}, config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, wantErr: true, @@ -209,8 +209,8 @@ func Test_bottleRocket_UpdateCredentialProvider(t *testing.T) { fields: fields{ client: http.Client{}, config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, patchResponse: response{ @@ -225,7 +225,7 @@ func Test_bottleRocket_UpdateCredentialProvider(t *testing.T) { fields: fields{ client: http.Client{}, config: constants.CredentialProviderConfigOptions{ - ImagePatterns: "123456789.dkr.ecr.test-region.amazonaws.com", + ImagePatterns: []string{"123456789.dkr.ecr.test-region.amazonaws.com"}, DefaultCacheDuration: "24h", }, }, @@ -236,13 +236,29 @@ func Test_bottleRocket_UpdateCredentialProvider(t *testing.T) { }, wantErr: false, }, + { + name: "multiple match images for credential provider", + fields: fields{ + client: http.Client{}, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: []string{"123456789.dkr.ecr.test-region.amazonaws.com", "987654321.dkr.ecr.test-region.amazonaws.com"}, + DefaultCacheDuration: "24h", + }, + }, + patchResponse: response{ + statusCode: http.StatusNoContent, + expectedBody: []byte("{\"kubernetes\":{\"credential-providers\":{\"ecr-credential-provider\":{\"cache-duration\":\"24h\",\"enabled\":true,\"image-patterns\":[\"123456789.dkr.ecr.test-region.amazonaws.com\",\"987654321.dkr.ecr.test-region.amazonaws.com\"]}}}}"), + responseMsg: "", + }, + wantErr: false, + }, { name: "failed credential provider update", fields: fields{ client: http.Client{}, config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, patchResponse: response{ @@ -305,16 +321,16 @@ func Test_bottleRocket_Initialize(t *testing.T) { args: args{ socketPath: "/test/path.sock", config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := NewBottleRocketConfigurator() - b.Initialize(tt.args.socketPath, tt.args.config) + b := NewBottleRocketConfigurator(tt.args.socketPath) + b.Initialize(tt.args.config) assert.Equal(t, tt.baseUrl, b.baseURL) assert.Equal(t, tt.args.config, b.config) assert.NotNil(t, b.client) diff --git a/credentialproviderpackage/pkg/configurator/configurator.go b/credentialproviderpackage/pkg/configurator/configurator.go index 04f160bf..cd28a06b 100644 --- a/credentialproviderpackage/pkg/configurator/configurator.go +++ b/credentialproviderpackage/pkg/configurator/configurator.go @@ -4,7 +4,7 @@ import "credential-provider/pkg/constants" type Configurator interface { // Initialize Handles node specific configuration depending on OS - Initialize(filepath string, config constants.CredentialProviderConfigOptions) + Initialize(config constants.CredentialProviderConfigOptions) // UpdateAWSCredentials Handles AWS Credential Setup UpdateAWSCredentials(sourcePath string, profile string) error diff --git a/credentialproviderpackage/pkg/configurator/linux/linux.go b/credentialproviderpackage/pkg/configurator/linux/linux.go index 9fef9088..6b4478bc 100644 --- a/credentialproviderpackage/pkg/configurator/linux/linux.go +++ b/credentialproviderpackage/pkg/configurator/linux/linux.go @@ -37,7 +37,7 @@ func NewLinuxConfigurator() *linuxOS { } } -func (c *linuxOS) Initialize(_ string, config constants.CredentialProviderConfigOptions) { +func (c *linuxOS) Initialize(config constants.CredentialProviderConfigOptions) { c.config = config } diff --git a/credentialproviderpackage/pkg/configurator/linux/linux_test.go b/credentialproviderpackage/pkg/configurator/linux/linux_test.go index 6430b012..17d05eed 100644 --- a/credentialproviderpackage/pkg/configurator/linux/linux_test.go +++ b/credentialproviderpackage/pkg/configurator/linux/linux_test.go @@ -39,8 +39,8 @@ func Test_linuxOS_updateKubeletArguments(t *testing.T) { extraArgsPath: dir, basePath: dir, config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, args: args{line: ""}, @@ -49,6 +49,24 @@ func Test_linuxOS_updateKubeletArguments(t *testing.T) { want: fmt.Sprintf(" --feature-gates=KubeletCredentialProviders=true "+ "--image-credential-provider-config=%s%s", dir, constants.CredProviderFile), }, + { + name: "test multiple match patterns", + fields: fields{ + profile: "eksa-packages", + extraArgsPath: dir, + basePath: dir, + config: constants.CredentialProviderConfigOptions{ + ImagePatterns: []string{"1234567.dkr.ecr.us-east-1.amazonaws.com", + "7654321.dkr.ecr.us-west-2.amazonaws.com"}, + DefaultCacheDuration: constants.DefaultCacheDuration, + }, + }, + args: args{line: ""}, + outputConfigPath: dir + "/" + constants.CredProviderFile, + configWantPath: "testdata/expected-config-multiple-patterns.yaml", + want: fmt.Sprintf(" --feature-gates=KubeletCredentialProviders=true "+ + "--image-credential-provider-config=%s%s", dir, constants.CredProviderFile), + }, { name: "skip credential provider if already provided", fields: fields{ @@ -56,8 +74,8 @@ func Test_linuxOS_updateKubeletArguments(t *testing.T) { extraArgsPath: dir, basePath: dir, config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, args: args{line: " --feature-gates=KubeletCredentialProviders=true"}, @@ -72,8 +90,8 @@ func Test_linuxOS_updateKubeletArguments(t *testing.T) { extraArgsPath: dir, basePath: dir, config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, args: args{line: " --feature-gates=KubeletCredentialProviders=false --image-credential-provider-config=blah"}, @@ -127,8 +145,8 @@ func Test_linuxOS_UpdateAWSCredentials(t *testing.T) { extraArgsPath: dir, basePath: dir, config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, args: args{ @@ -183,7 +201,6 @@ func Test_linuxOS_Initialize(t *testing.T) { config constants.CredentialProviderConfigOptions } type args struct { - in0 string config constants.CredentialProviderConfigOptions } tests := []struct { @@ -194,10 +211,9 @@ func Test_linuxOS_Initialize(t *testing.T) { { name: "simple initialization", args: args{ - in0: "", config: constants.CredentialProviderConfigOptions{ - ImagePatterns: constants.ImagePattern, - DefaultCacheDuration: constants.CacheDuration, + ImagePatterns: []string{constants.DefaultImagePattern}, + DefaultCacheDuration: constants.DefaultCacheDuration, }, }, }, @@ -205,7 +221,7 @@ func Test_linuxOS_Initialize(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewLinuxConfigurator() - c.Initialize(tt.args.in0, tt.args.config) + c.Initialize(tt.args.config) assert.Equal(t, c.config, tt.args.config) }) } diff --git a/credentialproviderpackage/pkg/configurator/linux/templates/credential-provider-config.yaml b/credentialproviderpackage/pkg/configurator/linux/templates/credential-provider-config.yaml index 4701c6f2..3dc29279 100644 --- a/credentialproviderpackage/pkg/configurator/linux/templates/credential-provider-config.yaml +++ b/credentialproviderpackage/pkg/configurator/linux/templates/credential-provider-config.yaml @@ -2,8 +2,8 @@ apiVersion: kubelet.config.k8s.io/v1alpha1 kind: CredentialProviderConfig providers: - name: ecr-credential-provider - matchImages: - - "{{.imagePattern}}" + matchImages:{{range $val := .imagePattern}} + - "{{$val}}"{{end}} defaultCacheDuration: "{{.cacheDuration}}" apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1 env: diff --git a/credentialproviderpackage/pkg/configurator/linux/testdata/expected-config-multiple-patterns.yaml b/credentialproviderpackage/pkg/configurator/linux/testdata/expected-config-multiple-patterns.yaml new file mode 100644 index 00000000..41f9a587 --- /dev/null +++ b/credentialproviderpackage/pkg/configurator/linux/testdata/expected-config-multiple-patterns.yaml @@ -0,0 +1,18 @@ +apiVersion: kubelet.config.k8s.io/v1alpha1 +kind: CredentialProviderConfig +providers: + - name: ecr-credential-provider + matchImages: + - "1234567.dkr.ecr.us-east-1.amazonaws.com" + - "7654321.dkr.ecr.us-west-2.amazonaws.com" + defaultCacheDuration: "30m" + apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1 + env: + - name: AWS_PROFILE + value: eksa-packages + - name: AWS_CONFIG_FILE + value: /eksa-packages/aws-creds + - name: PATH + value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/eksa-packages + - name: HOME + value: /eksa-packages/ diff --git a/credentialproviderpackage/pkg/constants/constants.go b/credentialproviderpackage/pkg/constants/constants.go index ec272ad5..b665859d 100644 --- a/credentialproviderpackage/pkg/constants/constants.go +++ b/credentialproviderpackage/pkg/constants/constants.go @@ -2,9 +2,9 @@ package constants const ( // Credential Provider constants - ImagePattern = "*.dkr.ecr.*.amazonaws.com" - CacheDuration = "30m" - CredProviderFile = "credential-provider-config.yaml" + DefaultImagePattern = "*.dkr.ecr.*.amazonaws.com" + DefaultCacheDuration = "30m" + CredProviderFile = "credential-provider-config.yaml" // Aws Credentials CredSrcPath = "/secrets/aws-creds/config" @@ -36,6 +36,6 @@ const ( ) type CredentialProviderConfigOptions struct { - ImagePatterns string + ImagePatterns []string DefaultCacheDuration string } From 4011c007253e65119d165f55531cd4365407ac77 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Thu, 23 Feb 2023 13:42:57 +0000 Subject: [PATCH 04/16] Namespacing serviceaccount.yaml and removing comments from values.yaml --- .../templates/serviceaccount.yaml | 1 + .../credential-provider-package/values.yaml | 17 ----------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/serviceaccount.yaml b/credentialproviderpackage/charts/credential-provider-package/templates/serviceaccount.yaml index 65ffbfdb..9d261be5 100644 --- a/credentialproviderpackage/charts/credential-provider-package/templates/serviceaccount.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/templates/serviceaccount.yaml @@ -3,6 +3,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "credential-provider.serviceAccountName" . }} + namespace: {{ .Release.Namespace | default .Values.defaultNamespace | quote }} labels: {{- include "credential-provider.labels" . | nindent 4 }} {{- with .Values.serviceAccount.annotations }} diff --git a/credentialproviderpackage/charts/credential-provider-package/values.yaml b/credentialproviderpackage/charts/credential-provider-package/values.yaml index ee2de9da..2184f8a3 100644 --- a/credentialproviderpackage/charts/credential-provider-package/values.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/values.yaml @@ -34,27 +34,10 @@ serviceAccount: podAnnotations: {} podSecurityContext: {} - # fsGroup: 2000 securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi nodeSelector: {} From 32cfebdff98a20ac82f5778b9ad38a134d0a06c4 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Thu, 23 Feb 2023 15:47:10 +0000 Subject: [PATCH 05/16] Copy binaries always to cover update case. Update references to docker to Amazon Linux 2 --- .../credential-provider-package/templates/_helpers.tpl | 2 +- .../credential-provider-package/templates/daemonset.yaml | 2 +- credentialproviderpackage/pkg/configurator/linux/linux.go | 8 ++++---- credentialproviderpackage/pkg/constants/constants.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/_helpers.tpl b/credentialproviderpackage/charts/credential-provider-package/templates/_helpers.tpl index eb3e20b8..629f9696 100644 --- a/credentialproviderpackage/charts/credential-provider-package/templates/_helpers.tpl +++ b/credentialproviderpackage/charts/credential-provider-package/templates/_helpers.tpl @@ -80,7 +80,7 @@ Function to figure out os name {{- if contains "Bottlerocket" .status.nodeInfo.osImage -}} {{- printf "bottlerocket" -}} {{- else if contains "Amazon Linux" .status.nodeInfo.osImage -}} -{{- printf "docker" -}} +{{- printf "amazonlinux" -}} {{- else -}} {{- printf "other" -}} {{- end }} diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml b/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml index cf082464..0547a089 100644 --- a/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml @@ -62,7 +62,7 @@ spec: - name: socket hostPath: path: /run/api.sock - {{- else if eq $os "docker"}} + {{- else if eq $os "amazonlinux"}} - name: kubelet-extra-args hostPath: path: /etc/default/kubelet diff --git a/credentialproviderpackage/pkg/configurator/linux/linux.go b/credentialproviderpackage/pkg/configurator/linux/linux.go index 6b4478bc..6d388ce2 100644 --- a/credentialproviderpackage/pkg/configurator/linux/linux.go +++ b/credentialproviderpackage/pkg/configurator/linux/linux.go @@ -211,11 +211,11 @@ func (c *linuxOS) updateKubeletArguments(line string) string { } args += val + val, err = copyBinaries() + if err != nil { + utils.ErrorLogger.Printf("Error coping binaries %v\n", err) + } if !strings.Contains(line, "image-credential-provider-bin-dir") { - val, err = copyBinaries() - if err != nil { - utils.ErrorLogger.Printf("Error coping binaries %v\n", err) - } args += val } } diff --git a/credentialproviderpackage/pkg/constants/constants.go b/credentialproviderpackage/pkg/constants/constants.go index b665859d..2a4205bb 100644 --- a/credentialproviderpackage/pkg/constants/constants.go +++ b/credentialproviderpackage/pkg/constants/constants.go @@ -29,7 +29,7 @@ const ( type OSType string const ( - Docker OSType = "docker" + AmazonLinux OSType = "amazonlinux" Ubuntu = "ubuntu" Redhat = "redhat" BottleRocket = "bottlerocket" From 09764a30fb977f1b208e269d8ea95822fd408859 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Tue, 28 Feb 2023 14:16:47 +0000 Subject: [PATCH 06/16] Adding AWS Profile to be configurable --- .../templates/daemonset.yaml | 2 ++ .../charts/credential-provider-package/values.yaml | 1 + .../cmd/aws-credential-provider/main.go | 10 +++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml b/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml index 0547a089..cd680fad 100644 --- a/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml @@ -49,6 +49,8 @@ spec: env: - name: OS_TYPE value: {{ template "template.getOSName" }} + - name: AWS_PROFILE + value: {{.Values.application.profile}} - name: MATCH_IMAGES value: '{{ join "," .Values.application.matchImages }}' - name: DEFAULT_CACHE_DURATION diff --git a/credentialproviderpackage/charts/credential-provider-package/values.yaml b/credentialproviderpackage/charts/credential-provider-package/values.yaml index 2184f8a3..b312b70b 100644 --- a/credentialproviderpackage/charts/credential-provider-package/values.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/values.yaml @@ -17,6 +17,7 @@ application: secretName: aws-cred matchImages: [] defaultCacheDuration: "" + profile: "" imagePullSecrets: [] nameOverride: "" diff --git a/credentialproviderpackage/cmd/aws-credential-provider/main.go b/credentialproviderpackage/cmd/aws-credential-provider/main.go index a8c62331..bc4e361b 100644 --- a/credentialproviderpackage/cmd/aws-credential-provider/main.go +++ b/credentialproviderpackage/cmd/aws-credential-provider/main.go @@ -33,6 +33,10 @@ func main() { utils.ErrorLogger.Println("Missing Environment Variable OS") os.Exit(1) } + profile := os.Getenv("AWS_PROFILE") + if profile == "" { + profile = constants.Profile + } config := createCredentialProviderConfigOptions() if osType == constants.BottleRocket { socket, err := os.Stat(constants.SocketPath) @@ -49,11 +53,11 @@ func main() { } configurator.Initialize(config) - err := configurator.UpdateAWSCredentials(constants.CredSrcPath, constants.Profile) + err := configurator.UpdateAWSCredentials(constants.CredSrcPath, profile) checkErrAndLog(err, utils.ErrorLogger) utils.InfoLogger.Println("Aws credentials configured") - err = configurator.UpdateCredentialProvider(constants.Profile) + err = configurator.UpdateCredentialProvider(profile) checkErrAndLog(err, utils.ErrorLogger) utils.InfoLogger.Println("Credential Provider Configured") @@ -79,7 +83,7 @@ func main() { } if event.Has(fsnotify.Create) { if event.Name == constants.CredWatchData { - err = configurator.UpdateAWSCredentials(constants.CredSrcPath, constants.Profile) + err = configurator.UpdateAWSCredentials(constants.CredSrcPath, profile) checkErrAndLog(err, utils.ErrorLogger) utils.InfoLogger.Println("Aws credentials successfully changed") } From 20cf973c0e7224a9342cf39480591b2031f0eb44 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Fri, 3 Mar 2023 18:42:32 +0000 Subject: [PATCH 07/16] Update go version to 1.19, cleanup chart, cleanup bottlerocket tests --- credentialproviderpackage/Dockerfile | 2 +- .../credential-provider-package/Chart.yaml | 23 ++------------ .../templates/daemonset.yaml | 2 +- .../cmd/aws-credential-provider/main.go | 31 ++++++++++--------- credentialproviderpackage/go.mod | 2 +- .../configurator/bottlerocket/bottlerocket.go | 4 +-- .../bottlerocket/bottlerocket_test.go | 21 ++++++++++--- 7 files changed, 42 insertions(+), 43 deletions(-) diff --git a/credentialproviderpackage/Dockerfile b/credentialproviderpackage/Dockerfile index 4660509b..6cadc0b1 100644 --- a/credentialproviderpackage/Dockerfile +++ b/credentialproviderpackage/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18-buster +FROM golang:1.19-buster ENV GOTRACEBACK=single ENV GOPROXY=direct WORKDIR /app diff --git a/credentialproviderpackage/charts/credential-provider-package/Chart.yaml b/credentialproviderpackage/charts/credential-provider-package/Chart.yaml index 499d9bf3..45fe512c 100644 --- a/credentialproviderpackage/charts/credential-provider-package/Chart.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/Chart.yaml @@ -1,24 +1,7 @@ apiVersion: v2 name: credential-provider-package -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. +description: A Helm chart for credential-provider-package, an application for configuring credentials via Kubelet Credential Provider type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" +sources: + - https://github.com/aws/eks-anywhere-packages/credentialproviderpackage diff --git a/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml b/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml index cd680fad..d4d49420 100644 --- a/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/templates/daemonset.yaml @@ -48,7 +48,7 @@ spec: {{- end}} env: - name: OS_TYPE - value: {{ template "template.getOSName" }} + value: {{ $os }} - name: AWS_PROFILE value: {{.Values.application.profile}} - name: MATCH_IMAGES diff --git a/credentialproviderpackage/cmd/aws-credential-provider/main.go b/credentialproviderpackage/cmd/aws-credential-provider/main.go index bc4e361b..97c8dc47 100644 --- a/credentialproviderpackage/cmd/aws-credential-provider/main.go +++ b/credentialproviderpackage/cmd/aws-credential-provider/main.go @@ -2,13 +2,11 @@ package main import ( _ "embed" + "github.com/fsnotify/fsnotify" "io/fs" "log" "os" "strings" - "time" - - "github.com/fsnotify/fsnotify" cfg "credential-provider/pkg/configurator" "credential-provider/pkg/configurator/bottlerocket" @@ -19,18 +17,16 @@ import ( func checkErrAndLog(err error, logger *log.Logger) { if err != nil { - logger.Println(err) + logger.Fatal(err) os.Exit(1) } } func main() { - utils.InfoLogger.Println("Running at " + time.Now().UTC().String()) - var configurator cfg.Configurator osType := strings.ToLower(os.Getenv("OS_TYPE")) if osType == "" { - utils.ErrorLogger.Println("Missing Environment Variable OS") + utils.ErrorLogger.Println("Missing Environment Variable OS_TYPE") os.Exit(1) } profile := os.Getenv("AWS_PROFILE") @@ -40,13 +36,14 @@ func main() { config := createCredentialProviderConfigOptions() if osType == constants.BottleRocket { socket, err := os.Stat(constants.SocketPath) - checkErrAndLog(err, utils.ErrorLogger) + if err != nil { + utils.ErrorLogger.Fatal(err) + } if socket.Mode().Type() == fs.ModeSocket { configurator = bottlerocket.NewBottleRocketConfigurator(constants.SocketPath) } else { - utils.ErrorLogger.Printf("Unexpected type %s expected socket\n", socket.Mode().Type()) - os.Exit(1) + utils.ErrorLogger.Fatalf("Unexpected type %s expected socket\n", socket.Mode().Type()) } } else { configurator = linux.NewLinuxConfigurator() @@ -54,22 +51,28 @@ func main() { configurator.Initialize(config) err := configurator.UpdateAWSCredentials(constants.CredSrcPath, profile) - checkErrAndLog(err, utils.ErrorLogger) + if err != nil { + utils.ErrorLogger.Fatal(err) + } utils.InfoLogger.Println("Aws credentials configured") err = configurator.UpdateCredentialProvider(profile) - checkErrAndLog(err, utils.ErrorLogger) + if err != nil { + utils.ErrorLogger.Fatal(err) + } utils.InfoLogger.Println("Credential Provider Configured") err = configurator.CommitChanges() - checkErrAndLog(err, utils.ErrorLogger) + if err != nil { + utils.ErrorLogger.Fatal(err) + } utils.InfoLogger.Println("Kubelet Restarted") // Creating watcher for credentials watcher, err := fsnotify.NewWatcher() if err != nil { - log.Fatal(err) + utils.ErrorLogger.Fatal(err) } defer watcher.Close() diff --git a/credentialproviderpackage/go.mod b/credentialproviderpackage/go.mod index 98f1d575..069ffee7 100644 --- a/credentialproviderpackage/go.mod +++ b/credentialproviderpackage/go.mod @@ -1,6 +1,6 @@ module credential-provider -go 1.18 +go 1.19 require ( github.com/fsnotify/fsnotify v1.6.0 diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go index aac462db..d3d6cc15 100644 --- a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go @@ -20,7 +20,7 @@ type bottleRocket struct { config constants.CredentialProviderConfigOptions } -type AwsCred struct { +type awsCred struct { Aws Aws `json:"aws"` } type Aws struct { @@ -142,7 +142,7 @@ func createCredentialsPayload(content string, profile string) ([]byte, error) { Profile: profile, } - creds := AwsCred{Aws: aws} + creds := awsCred{Aws: aws} payload, err := json.Marshal(creds) if err != nil { diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go index 52572089..ee9a5308 100644 --- a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go @@ -2,10 +2,13 @@ package bottlerocket import ( "bytes" + "encoding/base64" "fmt" + "io" "io/ioutil" "net/http" "net/http/httptest" + "os" "testing" "github.com/stretchr/testify/assert" @@ -83,6 +86,17 @@ func Test_bottleRocket_CommitChanges(t *testing.T) { } func Test_bottleRocket_UpdateAWSCredentials(t *testing.T) { + file, err := os.Open("testdata/testcreds") + if err != nil { + t.Errorf("Failed to open testcreds") + } + content, err := io.ReadAll(file) + if err != nil { + t.Errorf("Failed to read testcreds") + } + encodedSecret := base64.StdEncoding.EncodeToString(content) + expectedBody := fmt.Sprintf("{\"aws\":{\"config\":\"%s\",\"profile\":\"eksa-packages\",\"region\":\"\"}}", encodedSecret) + type fields struct { client http.Client baseURL string @@ -92,7 +106,6 @@ func Test_bottleRocket_UpdateAWSCredentials(t *testing.T) { path string profile string } - tests := []struct { name string fields fields @@ -113,7 +126,7 @@ func Test_bottleRocket_UpdateAWSCredentials(t *testing.T) { }, patchResponse: response{ statusCode: http.StatusNoContent, - expectedBody: []byte("{\"aws\":{\"config\":\"W3Byb2ZpbGUgZWtzYS1wYWNrYWdlc10KYXdzX2FjY2Vzc19rZXlfaWQ9QUtJQUlPU0ZPRE5ON0VYQU1QTEUKYXdzX3NlY3JldF9hY2Nlc3Nfa2V5PXdKYWxyWFV0bkZFTUkvSzdNREVORy9iUHhSZmlDWUVYQU1QTEVLRVk=\",\"profile\":\"eksa-packages\",\"region\":\"\"}}"), + expectedBody: []byte(expectedBody), responseMsg: "", }, commitResponse: response{ @@ -134,7 +147,7 @@ func Test_bottleRocket_UpdateAWSCredentials(t *testing.T) { }, patchResponse: response{ statusCode: http.StatusNoContent, - expectedBody: []byte("{\"aws\":{\"config\":\"W3Byb2ZpbGUgZWtzYS1wYWNrYWdlc10KYXdzX2FjY2Vzc19rZXlfaWQ9QUtJQUlPU0ZPRE5ON0VYQU1QTEUKYXdzX3NlY3JldF9hY2Nlc3Nfa2V5PXdKYWxyWFV0bkZFTUkvSzdNREVORy9iUHhSZmlDWUVYQU1QTEVLRVk=\",\"profile\":\"eksa-packages\",\"region\":\"\"}}"), + expectedBody: []byte(expectedBody), responseMsg: "", }, commitResponse: response{ @@ -155,7 +168,7 @@ func Test_bottleRocket_UpdateAWSCredentials(t *testing.T) { }, patchResponse: response{ statusCode: http.StatusNotFound, - expectedBody: []byte("{\"aws\":{\"config\":\"W3Byb2ZpbGUgZWtzYS1wYWNrYWdlc10KYXdzX2FjY2Vzc19rZXlfaWQ9QUtJQUlPU0ZPRE5ON0VYQU1QTEUKYXdzX3NlY3JldF9hY2Nlc3Nfa2V5PXdKYWxyWFV0bkZFTUkvSzdNREVORy9iUHhSZmlDWUVYQU1QTEVLRVk=\",\"profile\":\"eksa-packages\",\"region\":\"\"}}"), + expectedBody: []byte(expectedBody), responseMsg: "", }, commitResponse: response{ From e0fc178f286780bed5574b7d9bc9364c41139b0a Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Fri, 3 Mar 2023 19:02:26 +0000 Subject: [PATCH 08/16] Move util to pkg/log. Update to go 1.19 in makefile --- credentialproviderpackage/Makefile | 2 +- .../cmd/aws-credential-provider/main.go | 40 ++++++++----------- .../pkg/configurator/linux/linux.go | 6 +-- credentialproviderpackage/pkg/log/log.go | 18 +++++++++ credentialproviderpackage/pkg/utils/utils.go | 18 --------- 5 files changed, 39 insertions(+), 45 deletions(-) create mode 100644 credentialproviderpackage/pkg/log/log.go delete mode 100644 credentialproviderpackage/pkg/utils/utils.go diff --git a/credentialproviderpackage/Makefile b/credentialproviderpackage/Makefile index a87e28d2..f445d40d 100644 --- a/credentialproviderpackage/Makefile +++ b/credentialproviderpackage/Makefile @@ -2,7 +2,7 @@ SHELL = /usr/bin/env bash -o pipefail .SHELLFLAGS = -ec REPO_ROOT=$(shell git rev-parse --show-toplevel)/credentialproviderpackage -GOLANG_VERSION?="1.18" +GOLANG_VERSION?="1.19" #GO ?= $(shell source $(REPO_ROOT)/scripts/common.sh && build::common::get_go_path $(GOLANG_VERSION))/go GO = go diff --git a/credentialproviderpackage/cmd/aws-credential-provider/main.go b/credentialproviderpackage/cmd/aws-credential-provider/main.go index 97c8dc47..8db9590f 100644 --- a/credentialproviderpackage/cmd/aws-credential-provider/main.go +++ b/credentialproviderpackage/cmd/aws-credential-provider/main.go @@ -4,7 +4,6 @@ import ( _ "embed" "github.com/fsnotify/fsnotify" "io/fs" - "log" "os" "strings" @@ -12,21 +11,14 @@ import ( "credential-provider/pkg/configurator/bottlerocket" "credential-provider/pkg/configurator/linux" "credential-provider/pkg/constants" - "credential-provider/pkg/utils" + "credential-provider/pkg/log" ) -func checkErrAndLog(err error, logger *log.Logger) { - if err != nil { - logger.Fatal(err) - os.Exit(1) - } -} - func main() { var configurator cfg.Configurator osType := strings.ToLower(os.Getenv("OS_TYPE")) if osType == "" { - utils.ErrorLogger.Println("Missing Environment Variable OS_TYPE") + log.ErrorLogger.Println("Missing Environment Variable OS_TYPE") os.Exit(1) } profile := os.Getenv("AWS_PROFILE") @@ -37,13 +29,13 @@ func main() { if osType == constants.BottleRocket { socket, err := os.Stat(constants.SocketPath) if err != nil { - utils.ErrorLogger.Fatal(err) + log.ErrorLogger.Fatal(err) } if socket.Mode().Type() == fs.ModeSocket { configurator = bottlerocket.NewBottleRocketConfigurator(constants.SocketPath) } else { - utils.ErrorLogger.Fatalf("Unexpected type %s expected socket\n", socket.Mode().Type()) + log.ErrorLogger.Fatalf("Unexpected type %s expected socket\n", socket.Mode().Type()) } } else { configurator = linux.NewLinuxConfigurator() @@ -52,27 +44,27 @@ func main() { configurator.Initialize(config) err := configurator.UpdateAWSCredentials(constants.CredSrcPath, profile) if err != nil { - utils.ErrorLogger.Fatal(err) + log.ErrorLogger.Fatal(err) } - utils.InfoLogger.Println("Aws credentials configured") + log.InfoLogger.Println("Aws credentials configured") err = configurator.UpdateCredentialProvider(profile) if err != nil { - utils.ErrorLogger.Fatal(err) + log.ErrorLogger.Fatal(err) } - utils.InfoLogger.Println("Credential Provider Configured") + log.InfoLogger.Println("Credential Provider Configured") err = configurator.CommitChanges() if err != nil { - utils.ErrorLogger.Fatal(err) + log.ErrorLogger.Fatal(err) } - utils.InfoLogger.Println("Kubelet Restarted") + log.InfoLogger.Println("Kubelet Restarted") // Creating watcher for credentials watcher, err := fsnotify.NewWatcher() if err != nil { - utils.ErrorLogger.Fatal(err) + log.ErrorLogger.Fatal(err) } defer watcher.Close() @@ -87,22 +79,24 @@ func main() { if event.Has(fsnotify.Create) { if event.Name == constants.CredWatchData { err = configurator.UpdateAWSCredentials(constants.CredSrcPath, profile) - checkErrAndLog(err, utils.ErrorLogger) - utils.InfoLogger.Println("Aws credentials successfully changed") + if err != nil { + log.ErrorLogger.Fatal(err) + } + log.InfoLogger.Println("Aws credentials successfully changed") } } case err, ok := <-watcher.Errors: if !ok { return } - log.Println("error:", err) + log.WarningLogger.Printf("filewatcher error: %v", err) } } }() err = watcher.Add(constants.CredWatchPath) if err != nil { - log.Fatal(err) + log.ErrorLogger.Fatal(err) } // Block main goroutine forever. diff --git a/credentialproviderpackage/pkg/configurator/linux/linux.go b/credentialproviderpackage/pkg/configurator/linux/linux.go index 6d388ce2..113b20f6 100644 --- a/credentialproviderpackage/pkg/configurator/linux/linux.go +++ b/credentialproviderpackage/pkg/configurator/linux/linux.go @@ -13,8 +13,8 @@ import ( "credential-provider/pkg/configurator" "credential-provider/pkg/constants" + "credential-provider/pkg/log" "credential-provider/pkg/templater" - "credential-provider/pkg/utils" ) //go:embed templates/credential-provider-config.yaml @@ -207,13 +207,13 @@ func (c *linuxOS) updateKubeletArguments(line string) string { if !strings.Contains(line, "image-credential-provider-config") { val, err := c.createConfig() if err != nil { - utils.ErrorLogger.Printf("Error creating configuration %v", err) + log.ErrorLogger.Printf("Error creating configuration %v", err) } args += val val, err = copyBinaries() if err != nil { - utils.ErrorLogger.Printf("Error coping binaries %v\n", err) + log.ErrorLogger.Printf("Error coping binaries %v\n", err) } if !strings.Contains(line, "image-credential-provider-bin-dir") { args += val diff --git a/credentialproviderpackage/pkg/log/log.go b/credentialproviderpackage/pkg/log/log.go new file mode 100644 index 00000000..b110586f --- /dev/null +++ b/credentialproviderpackage/pkg/log/log.go @@ -0,0 +1,18 @@ +package log + +import ( + "log" + "os" +) + +var ( + InfoLogger *log.Logger + WarningLogger *log.Logger + ErrorLogger *log.Logger +) + +func init() { + InfoLogger = log.New(os.Stdout, "INFO: ", log.Ltime|log.Lshortfile) + WarningLogger = log.New(os.Stderr, "WARNING: ", log.Ltime|log.Lshortfile) + ErrorLogger = log.New(os.Stderr, "ERROR: ", log.Ltime|log.Lshortfile) +} diff --git a/credentialproviderpackage/pkg/utils/utils.go b/credentialproviderpackage/pkg/utils/utils.go deleted file mode 100644 index 125e4b55..00000000 --- a/credentialproviderpackage/pkg/utils/utils.go +++ /dev/null @@ -1,18 +0,0 @@ -package utils - -import ( - "log" - "os" -) - -var ( - InfoLogger *log.Logger - WarningLogger *log.Logger - ErrorLogger *log.Logger -) - -func init() { - InfoLogger = log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile) - WarningLogger = log.New(os.Stderr, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile) - ErrorLogger = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile) -} From a82c2e1c7bf3a072eca0556e6b37da1234e9fd95 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Wed, 8 Mar 2023 14:57:59 +0000 Subject: [PATCH 09/16] Removing time from log and updating global reads to group reads --- .../pkg/configurator/linux/linux.go | 10 +++++----- credentialproviderpackage/pkg/log/log.go | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/credentialproviderpackage/pkg/configurator/linux/linux.go b/credentialproviderpackage/pkg/configurator/linux/linux.go index 113b20f6..383eec20 100644 --- a/credentialproviderpackage/pkg/configurator/linux/linux.go +++ b/credentialproviderpackage/pkg/configurator/linux/linux.go @@ -152,24 +152,24 @@ func copyWithPermissons(srcpath, dstpath string, permission os.FileMode) (err er func copyBinaries() (string, error) { srcPath := constants.BinPath + constants.ECRCredProviderBinary dstPath := constants.BasePath + constants.ECRCredProviderBinary - err := copyWithPermissons(srcPath, dstPath, 0744) + err := copyWithPermissons(srcPath, dstPath, 0700) if err != nil { return "", err } - err = os.Chmod(dstPath, 0744) + err = os.Chmod(dstPath, 0700) if err != nil { return "", err } srcPath = constants.BinPath + constants.IAMRolesSigningBinary dstPath = constants.BasePath + constants.IAMRolesSigningBinary - err = copyWithPermissons(srcPath, dstPath, 0744) + err = copyWithPermissons(srcPath, dstPath, 0700) if err != nil { return "", err } - err = os.Chmod(dstPath, 0744) + err = os.Chmod(dstPath, 0700) if err != nil { return "", err } @@ -191,7 +191,7 @@ func (c *linuxOS) createConfig() (string, error) { if err != nil { return "", nil } - err = ioutil.WriteFile(dstPath, bytes, 0644) + err = ioutil.WriteFile(dstPath, bytes, 0600) if err != nil { return "", err } diff --git a/credentialproviderpackage/pkg/log/log.go b/credentialproviderpackage/pkg/log/log.go index b110586f..049453a2 100644 --- a/credentialproviderpackage/pkg/log/log.go +++ b/credentialproviderpackage/pkg/log/log.go @@ -12,7 +12,7 @@ var ( ) func init() { - InfoLogger = log.New(os.Stdout, "INFO: ", log.Ltime|log.Lshortfile) - WarningLogger = log.New(os.Stderr, "WARNING: ", log.Ltime|log.Lshortfile) - ErrorLogger = log.New(os.Stderr, "ERROR: ", log.Ltime|log.Lshortfile) + InfoLogger = log.New(os.Stdout, "INFO: ", log.Lshortfile) + WarningLogger = log.New(os.Stderr, "WARNING: ", log.Lshortfile) + ErrorLogger = log.New(os.Stderr, "ERROR: ", log.Lshortfile) } From 24ea5ad6db159c8afddfdd4627b130f64827533a Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Wed, 8 Mar 2023 15:45:24 +0000 Subject: [PATCH 10/16] Updating Gosec --- .github/workflows/gosec.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gosec.yml b/.github/workflows/gosec.yml index 154758c1..0f620692 100644 --- a/.github/workflows/gosec.yml +++ b/.github/workflows/gosec.yml @@ -17,4 +17,4 @@ jobs: - name: Run Gosec Security Scanner uses: securego/gosec@master with: - args: --exclude-dir=kubetest-plugins --exclude-dir generatebundlefile --exclude-dir ecrtokenrefresher ./... + args: --exclude-dir=kubetest-plugins --exclude-dir generatebundlefile --exclude-dir ecrtokenrefresher --exclude-dir credentialproviderpackage ./... diff --git a/Makefile b/Makefile index b88dbb0a..bed4f9eb 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ vet: ## Run go vet against code. gosec: ## Run gosec against code. $(GO) install github.com/securego/gosec/v2/cmd/gosec@latest - gosec --exclude-dir generatebundlefile --exclude-dir ecrtokenrefresher ./... + gosec --exclude-dir generatebundlefile --exclude-dir ecrtokenrefresher --exclude-dir credentialproviderpackage ./... SIGNED_ARTIFACTS = pkg/signature/testdata/packagebundle_minControllerVersion.yaml.signed pkg/signature/testdata/packagebundle_valid.yaml.signed pkg/signature/testdata/pod_valid.yaml.signed api/testdata/bundle_one.yaml.signed api/testdata/bundle_two.yaml.signed ENVTEST_ASSETS_DIR=$(shell pwd)/testbin From e0e4d52c02421bdeccba0e21d5b4fedafd9743d6 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Wed, 8 Mar 2023 18:17:06 +0000 Subject: [PATCH 11/16] Update Makefile for go version --- credentialproviderpackage/Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/credentialproviderpackage/Makefile b/credentialproviderpackage/Makefile index f445d40d..af77b2b5 100644 --- a/credentialproviderpackage/Makefile +++ b/credentialproviderpackage/Makefile @@ -1,10 +1,10 @@ SHELL = /usr/bin/env bash -o pipefail .SHELLFLAGS = -ec -REPO_ROOT=$(shell git rev-parse --show-toplevel)/credentialproviderpackage +REPO_ROOT=$(shell git rev-parse --show-toplevel) +PROJECT_ROOT=$(REPO_ROOT)/credentialproviderpackage GOLANG_VERSION?="1.19" -#GO ?= $(shell source $(REPO_ROOT)/scripts/common.sh && build::common::get_go_path $(GOLANG_VERSION))/go -GO = go +GO ?= $(shell source $(REPO_ROOT)/scripts/common.sh && build::common::get_go_path $(GOLANG_VERSION))/go # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -26,9 +26,9 @@ clean: ## Clean output directory, and the built binary ##@ Build build: ## Build Binary - mkdir -p $(REPO_ROOT)/bin + mkdir -p $(PROJECT_ROOT)/bin $(GO) mod tidy -compat=$(GOLANG_VERSION) - $(GO) build -o $(REPO_ROOT)/bin/aws-credential-provider $(REPO_ROOT)/cmd/aws-credential-provider/*.go + $(GO) build -o $(PROJECT_ROOT)/bin/aws-credential-provider $(PROJECT_ROOT)/cmd/aws-credential-provider/*.go build-linux: [ -d bin ] || mkdir bin From 55c13a710110dbe4bc02aa8704738fb5fb4049b4 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Wed, 8 Mar 2023 20:38:30 +0000 Subject: [PATCH 12/16] Formatting fixes --- .../cmd/aws-credential-provider/main.go | 3 ++- .../configurator/bottlerocket/testdata/testcreds | 2 +- .../pkg/configurator/linux/linux.go | 15 +++------------ .../pkg/configurator/linux/testdata/testcreds | 2 +- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/credentialproviderpackage/cmd/aws-credential-provider/main.go b/credentialproviderpackage/cmd/aws-credential-provider/main.go index 8db9590f..f2d1ef9f 100644 --- a/credentialproviderpackage/cmd/aws-credential-provider/main.go +++ b/credentialproviderpackage/cmd/aws-credential-provider/main.go @@ -2,11 +2,12 @@ package main import ( _ "embed" - "github.com/fsnotify/fsnotify" "io/fs" "os" "strings" + "github.com/fsnotify/fsnotify" + cfg "credential-provider/pkg/configurator" "credential-provider/pkg/configurator/bottlerocket" "credential-provider/pkg/configurator/linux" diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/testdata/testcreds b/credentialproviderpackage/pkg/configurator/bottlerocket/testdata/testcreds index cec03763..cc4ea03f 100644 --- a/credentialproviderpackage/pkg/configurator/bottlerocket/testdata/testcreds +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/testdata/testcreds @@ -1,3 +1,3 @@ [profile eksa-packages] aws_access_key_id=AKIAIOSFODNN7EXAMPLE -aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \ No newline at end of file +aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY diff --git a/credentialproviderpackage/pkg/configurator/linux/linux.go b/credentialproviderpackage/pkg/configurator/linux/linux.go index 383eec20..60323961 100644 --- a/credentialproviderpackage/pkg/configurator/linux/linux.go +++ b/credentialproviderpackage/pkg/configurator/linux/linux.go @@ -79,10 +79,7 @@ func (c *linuxOS) UpdateCredentialProvider(_ string) error { out := strings.Join(lines, "\n") err = ioutil.WriteFile(c.extraArgsPath, []byte(out), 0644) - if err != nil { - return err - } - return nil + return err } func (c *linuxOS) CommitChanges() error { @@ -91,18 +88,12 @@ func (c *linuxOS) CommitChanges() error { return err } err = killProcess(process) - if err != nil { - return err - } - return nil + return err } func killProcess(process ps.Process) error { err := syscall.Kill(process.Pid(), syscall.SIGHUP) - if err != nil { - return err - } - return nil + return err } func findKubeletProcess() (ps.Process, error) { diff --git a/credentialproviderpackage/pkg/configurator/linux/testdata/testcreds b/credentialproviderpackage/pkg/configurator/linux/testdata/testcreds index cec03763..cc4ea03f 100644 --- a/credentialproviderpackage/pkg/configurator/linux/testdata/testcreds +++ b/credentialproviderpackage/pkg/configurator/linux/testdata/testcreds @@ -1,3 +1,3 @@ [profile eksa-packages] aws_access_key_id=AKIAIOSFODNN7EXAMPLE -aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \ No newline at end of file +aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY From 2aa00eeea4e437de7eada487aab40a9e36d018c6 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Thu, 9 Mar 2023 12:52:18 +0000 Subject: [PATCH 13/16] Allowed arm builds for linux --- credentialproviderpackage/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/credentialproviderpackage/Makefile b/credentialproviderpackage/Makefile index af77b2b5..9475500a 100644 --- a/credentialproviderpackage/Makefile +++ b/credentialproviderpackage/Makefile @@ -32,7 +32,7 @@ build: ## Build Binary build-linux: [ -d bin ] || mkdir bin - env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(MAKE) build + env CGO_ENABLED=0 GOOS=linux $(MAKE) build run: $(GO) run . From a7e35d07a15882bb0522e91f053fb07991ab5a00 Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Thu, 9 Mar 2023 13:40:53 +0000 Subject: [PATCH 14/16] Moving constants to individual files. Moved BR socket logic to BR constructor itself. --- .../cmd/aws-credential-provider/main.go | 33 ++++++++++-------- .../configurator/bottlerocket/bottlerocket.go | 19 ++++++++--- .../bottlerocket/bottlerocket_test.go | 6 ++-- .../pkg/configurator/linux/linux.go | 34 +++++++++++++------ .../pkg/configurator/linux/linux_test.go | 16 ++++----- .../pkg/constants/constants.go | 29 ---------------- 6 files changed, 66 insertions(+), 71 deletions(-) diff --git a/credentialproviderpackage/cmd/aws-credential-provider/main.go b/credentialproviderpackage/cmd/aws-credential-provider/main.go index f2d1ef9f..b1621f8a 100644 --- a/credentialproviderpackage/cmd/aws-credential-provider/main.go +++ b/credentialproviderpackage/cmd/aws-credential-provider/main.go @@ -2,7 +2,6 @@ package main import ( _ "embed" - "io/fs" "os" "strings" @@ -15,8 +14,20 @@ import ( "credential-provider/pkg/log" ) +const ( + bottleRocket = "bottlerocket" + socketPath = "/run/api.sock" + + // Aws Credentials + credSrcPath = "/secrets/aws-creds/config" + awsProfile = "eksa-packages" + credWatchData = "/secrets/aws-creds/..data" + credWatchPath = "/secrets/aws-creds/" +) + func main() { var configurator cfg.Configurator + var err error osType := strings.ToLower(os.Getenv("OS_TYPE")) if osType == "" { log.ErrorLogger.Println("Missing Environment Variable OS_TYPE") @@ -24,26 +35,20 @@ func main() { } profile := os.Getenv("AWS_PROFILE") if profile == "" { - profile = constants.Profile + profile = awsProfile } config := createCredentialProviderConfigOptions() - if osType == constants.BottleRocket { - socket, err := os.Stat(constants.SocketPath) + if osType == bottleRocket { + configurator, err = bottlerocket.NewBottleRocketConfigurator(socketPath) if err != nil { log.ErrorLogger.Fatal(err) } - if socket.Mode().Type() == fs.ModeSocket { - configurator = bottlerocket.NewBottleRocketConfigurator(constants.SocketPath) - - } else { - log.ErrorLogger.Fatalf("Unexpected type %s expected socket\n", socket.Mode().Type()) - } } else { configurator = linux.NewLinuxConfigurator() } configurator.Initialize(config) - err := configurator.UpdateAWSCredentials(constants.CredSrcPath, profile) + err = configurator.UpdateAWSCredentials(credSrcPath, profile) if err != nil { log.ErrorLogger.Fatal(err) } @@ -78,8 +83,8 @@ func main() { return } if event.Has(fsnotify.Create) { - if event.Name == constants.CredWatchData { - err = configurator.UpdateAWSCredentials(constants.CredSrcPath, profile) + if event.Name == credWatchData { + err = configurator.UpdateAWSCredentials(credSrcPath, profile) if err != nil { log.ErrorLogger.Fatal(err) } @@ -95,7 +100,7 @@ func main() { } }() - err = watcher.Add(constants.CredWatchPath) + err = watcher.Add(credWatchPath) if err != nil { log.ErrorLogger.Fatal(err) } diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go index d3d6cc15..93adb9e4 100644 --- a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket.go @@ -6,9 +6,11 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io/fs" "io/ioutil" "net" "net/http" + "os" "credential-provider/pkg/configurator" "credential-provider/pkg/constants" @@ -21,9 +23,9 @@ type bottleRocket struct { } type awsCred struct { - Aws Aws `json:"aws"` + Aws aws `json:"aws"` } -type Aws struct { +type aws struct { Config string `json:"config"` Profile string `json:"profile"` Region string `json:"region"` @@ -46,7 +48,14 @@ type kubernetes struct { var _ configurator.Configurator = (*bottleRocket)(nil) -func NewBottleRocketConfigurator(socketPath string) *bottleRocket { +func NewBottleRocketConfigurator(socketPath string) (*bottleRocket, error) { + socket, err := os.Stat(socketPath) + if err != nil { + return nil, err + } + if socket.Mode().Type() != fs.ModeSocket { + return nil, fmt.Errorf("Unexpected type %s expected socket\n", socket.Mode().Type()) + } return &bottleRocket{ client: http.Client{ Transport: &http.Transport{ @@ -55,7 +64,7 @@ func NewBottleRocketConfigurator(socketPath string) *bottleRocket { }, }, }, - } + }, nil } func (b *bottleRocket) Initialize(config constants.CredentialProviderConfigOptions) { @@ -137,7 +146,7 @@ func (b *bottleRocket) sendSettingsSetRequest(payload []byte) error { } func createCredentialsPayload(content string, profile string) ([]byte, error) { - aws := Aws{ + aws := aws{ Config: content, Profile: profile, } diff --git a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go index ee9a5308..6490a06e 100644 --- a/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go +++ b/credentialproviderpackage/pkg/configurator/bottlerocket/bottlerocket_test.go @@ -320,8 +320,7 @@ func validatePatchRequest(w http.ResponseWriter, r *http.Request, t *testing.T, func Test_bottleRocket_Initialize(t *testing.T) { type args struct { - socketPath string - config constants.CredentialProviderConfigOptions + config constants.CredentialProviderConfigOptions } tests := []struct { name string @@ -332,7 +331,6 @@ func Test_bottleRocket_Initialize(t *testing.T) { name: "simple initialization", baseUrl: "http://localhost/", args: args{ - socketPath: "/test/path.sock", config: constants.CredentialProviderConfigOptions{ ImagePatterns: []string{constants.DefaultImagePattern}, DefaultCacheDuration: constants.DefaultCacheDuration, @@ -342,7 +340,7 @@ func Test_bottleRocket_Initialize(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := NewBottleRocketConfigurator(tt.args.socketPath) + b := &bottleRocket{} b.Initialize(tt.args.config) assert.Equal(t, tt.baseUrl, b.baseURL) assert.Equal(t, tt.args.config, b.config) diff --git a/credentialproviderpackage/pkg/configurator/linux/linux.go b/credentialproviderpackage/pkg/configurator/linux/linux.go index 60323961..a493927f 100644 --- a/credentialproviderpackage/pkg/configurator/linux/linux.go +++ b/credentialproviderpackage/pkg/configurator/linux/linux.go @@ -20,6 +20,18 @@ import ( //go:embed templates/credential-provider-config.yaml var credProviderTemplate string +const ( + binPath = "/eksa-binaries/" + basePath = "/eksa-packages/" + credOutFile = "aws-creds" + mountedExtraArgs = "/node-files/kubelet-extra-args" + credProviderFile = "credential-provider-config.yaml" + + // Binaries + ecrCredProviderBinary = "ecr-credential-provider" + iamRolesSigningBinary = "aws_signing_helper" +) + type linuxOS struct { profile string extraArgsPath string @@ -32,8 +44,8 @@ var _ configurator.Configurator = (*linuxOS)(nil) func NewLinuxConfigurator() *linuxOS { return &linuxOS{ profile: "", - extraArgsPath: constants.MountedExtraArgs, - basePath: constants.BasePath, + extraArgsPath: mountedExtraArgs, + basePath: basePath, } } @@ -43,7 +55,7 @@ func (c *linuxOS) Initialize(config constants.CredentialProviderConfigOptions) { func (c *linuxOS) UpdateAWSCredentials(sourcePath string, profile string) error { c.profile = profile - dstPath := c.basePath + constants.CredOutFile + dstPath := c.basePath + credOutFile err := copyWithPermissons(sourcePath, dstPath, 0600) return err @@ -141,8 +153,8 @@ func copyWithPermissons(srcpath, dstpath string, permission os.FileMode) (err er } func copyBinaries() (string, error) { - srcPath := constants.BinPath + constants.ECRCredProviderBinary - dstPath := constants.BasePath + constants.ECRCredProviderBinary + srcPath := binPath + ecrCredProviderBinary + dstPath := basePath + ecrCredProviderBinary err := copyWithPermissons(srcPath, dstPath, 0700) if err != nil { return "", err @@ -153,8 +165,8 @@ func copyBinaries() (string, error) { return "", err } - srcPath = constants.BinPath + constants.IAMRolesSigningBinary - dstPath = constants.BasePath + constants.IAMRolesSigningBinary + srcPath = binPath + iamRolesSigningBinary + dstPath = basePath + iamRolesSigningBinary err = copyWithPermissons(srcPath, dstPath, 0700) if err != nil { return "", err @@ -164,19 +176,19 @@ func copyBinaries() (string, error) { if err != nil { return "", err } - return fmt.Sprintf(" --image-credential-provider-bin-dir=%s", constants.BasePath), nil + return fmt.Sprintf(" --image-credential-provider-bin-dir=%s", basePath), nil } func (c *linuxOS) createConfig() (string, error) { values := map[string]interface{}{ "profile": c.profile, - "config": constants.BasePath + constants.CredOutFile, - "home": constants.BasePath, + "config": basePath + credOutFile, + "home": basePath, "imagePattern": c.config.ImagePatterns, "cacheDuration": c.config.DefaultCacheDuration, } - dstPath := c.basePath + constants.CredProviderFile + dstPath := c.basePath + credProviderFile bytes, err := templater.Execute(credProviderTemplate, values) if err != nil { diff --git a/credentialproviderpackage/pkg/configurator/linux/linux_test.go b/credentialproviderpackage/pkg/configurator/linux/linux_test.go index 17d05eed..9ad90002 100644 --- a/credentialproviderpackage/pkg/configurator/linux/linux_test.go +++ b/credentialproviderpackage/pkg/configurator/linux/linux_test.go @@ -44,10 +44,10 @@ func Test_linuxOS_updateKubeletArguments(t *testing.T) { }, }, args: args{line: ""}, - outputConfigPath: dir + "/" + constants.CredProviderFile, + outputConfigPath: dir + "/" + credProviderFile, configWantPath: "testdata/expected-config.yaml", want: fmt.Sprintf(" --feature-gates=KubeletCredentialProviders=true "+ - "--image-credential-provider-config=%s%s", dir, constants.CredProviderFile), + "--image-credential-provider-config=%s%s", dir, credProviderFile), }, { name: "test multiple match patterns", @@ -62,10 +62,10 @@ func Test_linuxOS_updateKubeletArguments(t *testing.T) { }, }, args: args{line: ""}, - outputConfigPath: dir + "/" + constants.CredProviderFile, + outputConfigPath: dir + "/" + credProviderFile, configWantPath: "testdata/expected-config-multiple-patterns.yaml", want: fmt.Sprintf(" --feature-gates=KubeletCredentialProviders=true "+ - "--image-credential-provider-config=%s%s", dir, constants.CredProviderFile), + "--image-credential-provider-config=%s%s", dir, credProviderFile), }, { name: "skip credential provider if already provided", @@ -79,9 +79,9 @@ func Test_linuxOS_updateKubeletArguments(t *testing.T) { }, }, args: args{line: " --feature-gates=KubeletCredentialProviders=true"}, - outputConfigPath: dir + "/" + constants.CredProviderFile, + outputConfigPath: dir + "/" + credProviderFile, configWantPath: "testdata/expected-config.yaml", - want: fmt.Sprintf(" --image-credential-provider-config=%s%s", dir, constants.CredProviderFile), + want: fmt.Sprintf(" --image-credential-provider-config=%s%s", dir, credProviderFile), }, { name: "skip both cred provider and feature gate if provided", @@ -95,7 +95,7 @@ func Test_linuxOS_updateKubeletArguments(t *testing.T) { }, }, args: args{line: " --feature-gates=KubeletCredentialProviders=false --image-credential-provider-config=blah"}, - outputConfigPath: dir + "/" + constants.CredProviderFile, + outputConfigPath: dir + "/" + credProviderFile, configWantPath: "", want: "", }, @@ -158,7 +158,7 @@ func Test_linuxOS_UpdateAWSCredentials(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dstFile := tt.fields.basePath + constants.CredOutFile + dstFile := tt.fields.basePath + credOutFile c := &linuxOS{ profile: tt.fields.profile, extraArgsPath: tt.fields.extraArgsPath, diff --git a/credentialproviderpackage/pkg/constants/constants.go b/credentialproviderpackage/pkg/constants/constants.go index 2a4205bb..41a04ea2 100644 --- a/credentialproviderpackage/pkg/constants/constants.go +++ b/credentialproviderpackage/pkg/constants/constants.go @@ -4,35 +4,6 @@ const ( // Credential Provider constants DefaultImagePattern = "*.dkr.ecr.*.amazonaws.com" DefaultCacheDuration = "30m" - CredProviderFile = "credential-provider-config.yaml" - - // Aws Credentials - CredSrcPath = "/secrets/aws-creds/config" - Profile = "eksa-packages" - CredWatchData = "/secrets/aws-creds/..data" - CredWatchPath = "/secrets/aws-creds/" - - // BottleRocket - SocketPath = "/run/api.sock" - - // Linux - BinPath = "/eksa-binaries/" - BasePath = "/eksa-packages/" - CredOutFile = "aws-creds" - MountedExtraArgs = "/node-files/kubelet-extra-args" - - // Binaries - ECRCredProviderBinary = "ecr-credential-provider" - IAMRolesSigningBinary = "aws_signing_helper" -) - -type OSType string - -const ( - AmazonLinux OSType = "amazonlinux" - Ubuntu = "ubuntu" - Redhat = "redhat" - BottleRocket = "bottlerocket" ) type CredentialProviderConfigOptions struct { From 0b49d95af10be432e9e12f5e4916592a1d27e84c Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Mon, 13 Mar 2023 13:53:05 +0000 Subject: [PATCH 15/16] Removing unused replicas in values.yaml --- .../charts/credential-provider-package/values.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/credentialproviderpackage/charts/credential-provider-package/values.yaml b/credentialproviderpackage/charts/credential-provider-package/values.yaml index b312b70b..24745371 100644 --- a/credentialproviderpackage/charts/credential-provider-package/values.yaml +++ b/credentialproviderpackage/charts/credential-provider-package/values.yaml @@ -1,7 +1,6 @@ # Default values for credential-provider. # This is a YAML-formatted file. -replicaCount: 1 # -- sourceRegistry for all container images in chart. sourceRegistry: public.ecr.aws/eks-anywhere defaultNamespace: eksa-packages From 1147cb23919a9c02da166b58e94b5ac7ae3a4dad Mon Sep 17 00:00:00 2001 From: Jun Shun Zhang <junshun@amazon.com> Date: Mon, 13 Mar 2023 19:19:48 +0000 Subject: [PATCH 16/16] Adding new lines to partialyaml and updated tests --- credentialproviderpackage/pkg/templater/partialyaml.go | 2 -- .../pkg/templater/testdata/invalid_template.yaml | 2 +- .../pkg/templater/testdata/key4_template.yaml | 2 +- .../pkg/templater/testdata/partial_yaml_array_expected.yaml | 2 +- .../pkg/templater/testdata/partial_yaml_map_expected.yaml | 2 +- .../pkg/templater/testdata/partial_yaml_object_expected.yaml | 2 +- .../pkg/templater/testdata/test1_conditional_false_want.yaml | 1 + .../pkg/templater/testdata/test1_conditional_true_want.yaml | 1 + .../pkg/templater/testdata/test1_template.yaml | 2 +- .../pkg/templater/testdata/test_indent_template.yaml | 2 +- .../pkg/templater/testdata/test_indent_want.yaml | 1 + 11 files changed, 10 insertions(+), 9 deletions(-) diff --git a/credentialproviderpackage/pkg/templater/partialyaml.go b/credentialproviderpackage/pkg/templater/partialyaml.go index 44112b27..053caf03 100644 --- a/credentialproviderpackage/pkg/templater/partialyaml.go +++ b/credentialproviderpackage/pkg/templater/partialyaml.go @@ -2,7 +2,6 @@ package templater import ( "reflect" - "strings" "sigs.k8s.io/yaml" ) @@ -25,7 +24,6 @@ func (p PartialYaml) ToYaml() (string, error) { return "", err } s := string(b) - s = strings.TrimSuffix(s, "\n") return s, nil } diff --git a/credentialproviderpackage/pkg/templater/testdata/invalid_template.yaml b/credentialproviderpackage/pkg/templater/testdata/invalid_template.yaml index 5dc5cbfc..d4010323 100644 --- a/credentialproviderpackage/pkg/templater/testdata/invalid_template.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/invalid_template.yaml @@ -2,4 +2,4 @@ key1: {{ .Key1 }} key2: {{ .Key2 {{ if .Conditional }} key3: {{ .Key3 }} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/credentialproviderpackage/pkg/templater/testdata/key4_template.yaml b/credentialproviderpackage/pkg/templater/testdata/key4_template.yaml index c65df82d..1cb80cf9 100644 --- a/credentialproviderpackage/pkg/templater/testdata/key4_template.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/key4_template.yaml @@ -3,4 +3,4 @@ key2: {{ .Key2 }} {{ if .Conditional }} key3: {{ .Key3 }} {{ end }} -key4: {{ .Key4 }} \ No newline at end of file +key4: {{ .Key4 }} diff --git a/credentialproviderpackage/pkg/templater/testdata/partial_yaml_array_expected.yaml b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_array_expected.yaml index d492ba49..3d6ff659 100644 --- a/credentialproviderpackage/pkg/templater/testdata/partial_yaml_array_expected.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_array_expected.yaml @@ -5,4 +5,4 @@ key3: - value array 2 key4: - key_in_nest_array: value - key_in_nest_array_2: 22 \ No newline at end of file + key_in_nest_array_2: 22 diff --git a/credentialproviderpackage/pkg/templater/testdata/partial_yaml_map_expected.yaml b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_map_expected.yaml index 37c88490..e8948147 100644 --- a/credentialproviderpackage/pkg/templater/testdata/partial_yaml_map_expected.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_map_expected.yaml @@ -5,4 +5,4 @@ key3: key_nest2: value nest 2 key4: key_nest1: value nest - key_nest2: 22 \ No newline at end of file + key_nest2: 22 diff --git a/credentialproviderpackage/pkg/templater/testdata/partial_yaml_object_expected.yaml b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_object_expected.yaml index 6edb56eb..c3c4509a 100644 --- a/credentialproviderpackage/pkg/templater/testdata/partial_yaml_object_expected.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/partial_yaml_object_expected.yaml @@ -1,3 +1,3 @@ key1: value 1 key2: 2 -key3: value3 \ No newline at end of file +key3: value3 diff --git a/credentialproviderpackage/pkg/templater/testdata/test1_conditional_false_want.yaml b/credentialproviderpackage/pkg/templater/testdata/test1_conditional_false_want.yaml index d6154a35..cedb76a4 100644 --- a/credentialproviderpackage/pkg/templater/testdata/test1_conditional_false_want.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/test1_conditional_false_want.yaml @@ -1,2 +1,3 @@ key1: value_1 key2: value_2 + diff --git a/credentialproviderpackage/pkg/templater/testdata/test1_conditional_true_want.yaml b/credentialproviderpackage/pkg/templater/testdata/test1_conditional_true_want.yaml index f1cfcb93..7116c47d 100644 --- a/credentialproviderpackage/pkg/templater/testdata/test1_conditional_true_want.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/test1_conditional_true_want.yaml @@ -2,3 +2,4 @@ key1: value_1 key2: value_2 key3: value_3 + diff --git a/credentialproviderpackage/pkg/templater/testdata/test1_template.yaml b/credentialproviderpackage/pkg/templater/testdata/test1_template.yaml index 113d3ca7..9e60bf61 100644 --- a/credentialproviderpackage/pkg/templater/testdata/test1_template.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/test1_template.yaml @@ -2,4 +2,4 @@ key1: {{ .Key1 }} key2: {{ .Key2 }} {{ if .Conditional }} key3: {{ .Key3 }} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/credentialproviderpackage/pkg/templater/testdata/test_indent_template.yaml b/credentialproviderpackage/pkg/templater/testdata/test_indent_template.yaml index 4393f80b..d8bfae53 100644 --- a/credentialproviderpackage/pkg/templater/testdata/test_indent_template.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/test_indent_template.yaml @@ -2,4 +2,4 @@ key1: {{ .Key1 }} key2: {{ .Key2 }} {{ if .Conditional }} {{ .KeyAndValue3 | indent 2 }} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/credentialproviderpackage/pkg/templater/testdata/test_indent_want.yaml b/credentialproviderpackage/pkg/templater/testdata/test_indent_want.yaml index 6d4ebdbf..94a0244c 100644 --- a/credentialproviderpackage/pkg/templater/testdata/test_indent_want.yaml +++ b/credentialproviderpackage/pkg/templater/testdata/test_indent_want.yaml @@ -2,3 +2,4 @@ key1: value_1 key2: value_2 key3: value_3 +