Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Adding Credential Provider Package with source and helm chart #829

Merged
merged 16 commits into from Mar 14, 2023

Conversation

junshun
Copy link
Member

@junshun junshun commented Feb 21, 2023

*Issue #717

Description of changes:
Adding source code and helm chart for credential provider package. This package provides nodes with credentials access to private registries using Kubernetes Credential Provider. This inital commit will only support Elastic Container Registry.

This package will take a aws config secret on the cluster and handle creation of the KubeletCredentialProviderConfig, putting binaries on the individual nodes, and setting the flags on the kubelet using a Daemonset.

For Bottlerocket there specific API is used to trigger the changes necessary on each node. For other OS a directory is mounted on the node to put the necessary files for authentication.

Notes For Review

credentialproviderpackage/charts - includes the helm chart following the format of other helm charts for packages. There is also a helper function added to detect OS which is used to mount file paths depending on the OS.

credentialproviderpackage/pkg/filewriter
credentialproviderpackage/pkg/templater
These two were manually brought over from eks-anywhere as to not have to force a dependency on them.

credentialproviderpackage/skaffold.yaml
Used to help locally test and debug changes

@eks-distro-bot
Copy link
Collaborator

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@junshun
Copy link
Member Author

junshun commented Feb 21, 2023

/hold

@junshun junshun marked this pull request as ready for review February 24, 2023 18:36
@junshun junshun changed the title [DRAFT] Adding Credential Provider Package with source and helm chart Adding Credential Provider Package with source and helm chart Feb 24, 2023
@junshun
Copy link
Member Author

junshun commented Feb 27, 2023

/unhold

@@ -0,0 +1,24 @@
apiVersion: skaffold/v3
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure we want this

Copy link
Member Author

Choose a reason for hiding this comment

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

This combined with the dockerfile helps enable locally testing. This package is otherwise hard to test locally as its meant to run on the node.

Copy link
Contributor

Choose a reason for hiding this comment

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

For vscode users maybe

Copy link
Member

Choose a reason for hiding this comment

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

I've never used skaffold. Might be worth doing a quick demo to see if it's a tool we might leverage.

credentialproviderpackage/pkg/utils/utils.go Outdated Show resolved Hide resolved
ARG SKAFFOLD_GO_GCFLAGS
RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o app cmd/aws-credential-provider/*.go

CMD ["/app/app"]
Copy link
Contributor

Choose a reason for hiding this comment

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

I never like that we have docker files, but don't use them.

Copy link
Member Author

Choose a reason for hiding this comment

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

In this case the dockerfile is useful for local testing as this binary can't really be tested locally since it requires the dependent binaries (ecr-credential-provider, iam-signing-helper) to copy.


build-linux:
[ -d bin ] || mkdir bin
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(MAKE) build
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't we need to build this for linux arm and amd?

Copy link
Member Author

Choose a reason for hiding this comment

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

I didn't think we supported ARM for the actual cluster but I can add it. Should work the same on ARM

Copy link
Member

Choose a reason for hiding this comment

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

We're not going to use this makefile for the actual image build right? This will be in build-tooling.

Copy link
Contributor

Choose a reason for hiding this comment

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

The package controller does do arch or os builds

build: go.sum generate ## Build package-manager binary.
    $(GO) build -o $(BIN_DIR)/package-manager main.go

checkErrAndLog(err, utils.ErrorLogger)

utils.InfoLogger.Println("Kubelet Restarted")

Copy link
Contributor

Choose a reason for hiding this comment

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

You could increase your test coverage a lot if this was broken up

Copy link
Member Author

Choose a reason for hiding this comment

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

How so?

Copy link
Contributor

Choose a reason for hiding this comment

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

This code isn't tested.

Copy link
Member Author

Choose a reason for hiding this comment

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

So you would like to see a test where the configurator interface is mocked this code is called from another package?

config constants.CredentialProviderConfigOptions
}

type AwsCred struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
type AwsCred struct {
type awsCred struct {

Maybe?

Copy link
Member

Choose a reason for hiding this comment

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

Why use this at all since it only contains one instance of Aws?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is used to match the bottlerocket api. I just generated the stucts based on the json.

@@ -0,0 +1,14 @@
FROM golang:1.18-buster
Copy link
Member

Choose a reason for hiding this comment

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

We want to use internal base images here. But why do we need a Dockerfile here, this will be in build-tooling.

Copy link
Member Author

Choose a reason for hiding this comment

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

So both this dockerfile and makefile are used for local testing. I am fine to remove it

@@ -0,0 +1,24 @@
apiVersion: v2
name: credential-provider-package
description: A Helm chart for Kubernetes
Copy link
Member

Choose a reason for hiding this comment

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

Clean up this file.

@@ -0,0 +1 @@
1. Installed Credential Provider Package
Copy link
Member

Choose a reason for hiding this comment

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

Can you remove this file?

{{- end}}
env:
- name: OS_TYPE
value: {{ template "template.getOSName" }}
Copy link
Member

Choose a reason for hiding this comment

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

reuse $os


build-linux:
[ -d bin ] || mkdir bin
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(MAKE) build
Copy link
Member

Choose a reason for hiding this comment

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

We're not going to use this makefile for the actual image build right? This will be in build-tooling.

{{- end}}
env:
- name: OS_TYPE
value: {{ template "template.getOSName" }}
Copy link
Member

Choose a reason for hiding this comment

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

reuse $os

}

func main() {
utils.InfoLogger.Println("Running at " + time.Now().UTC().String())
Copy link
Member

Choose a reason for hiding this comment

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

Don't print the date. Logs are already dated.

var configurator cfg.Configurator
osType := strings.ToLower(os.Getenv("OS_TYPE"))
if osType == "" {
utils.ErrorLogger.Println("Missing Environment Variable OS")
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
utils.ErrorLogger.Println("Missing Environment Variable OS")
utils.ErrorLogger.Println("Missing Environment Variable OS_TYPE")

)

func init() {
InfoLogger = log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
Copy link
Member

Choose a reason for hiding this comment

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

Don't include the date. Agree with Terry on this package.


func checkErrAndLog(err error, logger *log.Logger) {
if err != nil {
logger.Println(err)
Copy link
Member

Choose a reason for hiding this comment

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

If you're always doing os.EXIT(1), don't take a logger as input and call log.Fatal. Although repeatedly checking errors with if err != nil { ... } is annoying, this function call doesn't really improve it much if at all. I'd instead do the check directly and remove this.

configurator = bottlerocket.NewBottleRocketConfigurator(constants.SocketPath)

} else {
utils.ErrorLogger.Printf("Unexpected type %s expected socket\n", socket.Mode().Type())
Copy link
Member

Choose a reason for hiding this comment

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

Just call log.Fatal

@@ -0,0 +1,28 @@
module credential-provider

go 1.18
Copy link
Member

Choose a reason for hiding this comment

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

Use 1.19

config constants.CredentialProviderConfigOptions
}

type AwsCred struct {
Copy link
Member

Choose a reason for hiding this comment

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

Why use this at all since it only contains one instance of Aws?

type credentialProviders struct {
EcrCredentialProvider ecrCredentialProvider `json:"ecr-credential-provider"`
}
type kubernetes struct {
Copy link
Member

Choose a reason for hiding this comment

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

I see many structs that contain a single thing of another type. Why do we need those?

Copy link
Member Author

Choose a reason for hiding this comment

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

These structs were from br where they can have more in the structure, however I removed the used portions from ours.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think they are messages to BR

},
patchResponse: response{
statusCode: http.StatusNoContent,
expectedBody: []byte("{\"aws\":{\"config\":\"W3Byb2ZpbGUgZWtzYS1wYWNrYWdlc10KYXdzX2FjY2Vzc19rZXlfaWQ9QUtJQUlPU0ZPRE5ON0VYQU1QTEUKYXdzX3NlY3JldF9hY2Nlc3Nfa2V5PXdKYWxyWFV0bkZFTUkvSzdNREVORy9iUHhSZmlDWUVYQU1QTEVLRVk=\",\"profile\":\"eksa-packages\",\"region\":\"\"}}"),
Copy link
Member

Choose a reason for hiding this comment

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

Consider decoding this and calling the encode function before creating this object.


build-linux:
[ -d bin ] || mkdir bin
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(MAKE) build
Copy link
Contributor

Choose a reason for hiding this comment

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

The package controller does do arch or os builds

build: go.sum generate ## Build package-manager binary.
    $(GO) build -o $(BIN_DIR)/package-manager main.go

apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
env:
- name: AWS_PROFILE
value: eksa-packages
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
value: eksa-packages
value: {{.profile}}

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the test file that is matched against hence the values being written here instead of templating.

apiVersion: credentialprovider.kubelet.k8s.io/v1alpha1
env:
- name: AWS_PROFILE
value: eksa-packages
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
value: eksa-packages
value: {{.profile}}

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above, This is the test file that is matched against hence the values being written here instead of templating.

application:
secretName: aws-cred
matchImages: []
defaultCacheDuration: ""
Copy link
Contributor

Choose a reason for hiding this comment

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

should the chart provide a default duration?

Copy link
Member Author

Choose a reason for hiding this comment

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

I can include one, currently the application provides one if not included by chart

if osType == "" {
log.ErrorLogger.Println("Missing Environment Variable OS_TYPE")
os.Exit(1)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is fine, why not command line args?

Copy link
Member Author

Choose a reason for hiding this comment

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

I usually just fine environment variables easier to test with and setup IDEs as well

Copy link
Member

Choose a reason for hiding this comment

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

Command line args are better I think in this case. One can easily find the args that need to be used without needing to run the application vs in this case the application needs to be run to find out what is missing.

Copy link
Member Author

Choose a reason for hiding this comment

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

I find command line args are more difficult to test with tools such as scaffold where they try to override the entrypoint with a different one for debugging.

If there is strong feelings towards this let me know and I can change it to command line

}

out := strings.Join(lines, "\n")
err = ioutil.WriteFile(c.extraArgsPath, []byte(out), 0644)
Copy link
Contributor

Choose a reason for hiding this comment

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

World read?

Copy link
Member Author

Choose a reason for hiding this comment

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

This file is the KUBELET_EXTRA_ARGS file which by default is world read

func copyBinaries() (string, error) {
srcPath := constants.BinPath + constants.ECRCredProviderBinary
dstPath := constants.BasePath + constants.ECRCredProviderBinary
err := copyWithPermissons(srcPath, dstPath, 0744)
Copy link
Contributor

Choose a reason for hiding this comment

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

World read? If these are binaries, why not 755?


srcPath = constants.BinPath + constants.IAMRolesSigningBinary
dstPath = constants.BasePath + constants.IAMRolesSigningBinary
err = copyWithPermissons(srcPath, dstPath, 0744)
Copy link
Contributor

Choose a reason for hiding this comment

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

World read? Even group read I would question.

@@ -0,0 +1,41 @@
package constants

const (
Copy link
Contributor

Choose a reason for hiding this comment

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

Why globals? Each thing should have their own constants and most likely all or at least some of these can be lower case for reduced scope

@@ -0,0 +1,24 @@
apiVersion: skaffold/v3
Copy link
Contributor

Choose a reason for hiding this comment

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

For vscode users maybe

wantFile := ReadFile(t, wantPath)

if gotFile != wantFile {
cmd := exec.Command("diff", wantPath, gotPath)
Copy link
Member

Choose a reason for hiding this comment

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

Do we really need to diff? This adds a dependency to an OS binary which could be avoided by simply running a hash.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is a small price to pay for easy test debugging. Do we need to support running on windows?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is diff in test directory so it will not be in needed in the final image

Comment on lines 94 to 97
if err != nil {
return err
}
return nil
Copy link
Member

Choose a reason for hiding this comment

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

There are many instances of similar things across the PR. It doesn't affect functionality but the if is unnecessary.

Suggested change
if err != nil {
return err
}
return nil
return err

key2: {{ .Key2 }}
{{ if .Conditional }}
{{ .KeyAndValue3 | indent 2 }}
{{ end }}
Copy link
Member

Choose a reason for hiding this comment

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

no newline at EOF

Copy link
Member Author

Choose a reason for hiding this comment

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

This package and filewriter were copied from eks-anywhere. I would like to keep the consistency between the two

Copy link
Member

Choose a reason for hiding this comment

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

Text files need to end with \n under the unix spec. If this comes from the partialyaml.go trimsuffix, we should remove that or fix the logic.

)

func init() {
InfoLogger = log.New(os.Stdout, "INFO: ", log.Ltime|log.Lshortfile)
Copy link
Member

Choose a reason for hiding this comment

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

Remove the time.


REPO_ROOT=$(shell git rev-parse --show-toplevel)/credentialproviderpackage
GOLANG_VERSION?="1.19"
#GO ?= $(shell source $(REPO_ROOT)/scripts/common.sh && build::common::get_go_path $(GOLANG_VERSION))/go
Copy link
Member

Choose a reason for hiding this comment

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

Remove this if it is not used.

- name: aws-creds
mountPath: /secrets/aws-creds
{{ $os := include "template.getOSName" .}}
{{- if eq $os "bottlerocket" }}
Copy link
Member

Choose a reason for hiding this comment

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

I think we should reduce the logic here. Maybe move it to the helpers.tpl?

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as other comment

secret:
secretName: {{.Values.application.secretName}}
optional: false
{{- if eq $os "bottlerocket" }}
Copy link
Member

Choose a reason for hiding this comment

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

I think we should reduce the logic here. Maybe move it to the helpers.tpl?

Copy link
Member Author

Choose a reason for hiding this comment

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

Strongly prefer to keep this logic here. Makes the daemonset on its own much easier to read then having to figure out whats mounted from helpers.tpl


import (
_ "embed"
"github.com/fsnotify/fsnotify"
Copy link
Member

Choose a reason for hiding this comment

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

Can we try to run a gofmt on this and see if this is formatted correctly.

configurator = linux.NewLinuxConfigurator()
}

configurator.Initialize(config)
Copy link
Member

Choose a reason for hiding this comment

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

I second Terry on breaking this code down. And also I think the main file has much more code than usual. Maybe break this down into several easily readable files.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you guys have an example for what you would like to see. Currently the main file calls 5 functions, a constructor for the configurator, initializer, credential creator, create config, and commit changes. That is basically all the application really handles so I am not sure of the value of making it a seperate file.

@@ -0,0 +1,3 @@
[profile eksa-packages]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Copy link
Member

Choose a reason for hiding this comment

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

Add a new line.

@@ -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 ./...
Copy link
Member

Choose a reason for hiding this comment

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

Why do we have this?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is for the github workflows

Copy link
Member

Choose a reason for hiding this comment

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

Right, but why is gosec ignoring parts of our repo?

Copy link
Member Author

Choose a reason for hiding this comment

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

Created issue #870 to follow up on this

# Default values for credential-provider.
# This is a YAML-formatted file.

replicaCount: 1
Copy link
Member

Choose a reason for hiding this comment

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

This is most likely unused.

Copy link
Contributor

@TerryHowe TerryHowe left a comment

Choose a reason for hiding this comment

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

/lgtm

@eks-distro-bot
Copy link
Collaborator

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: junshun, lewisdiamond, TerryHowe

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:
  • OWNERS [TerryHowe,junshun,lewisdiamond]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@eks-distro-bot eks-distro-bot merged commit 318fba4 into aws:main Mar 14, 2023
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants