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

Support EKS Access entries #7421

Merged
merged 9 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ require (
github.com/tj/assert v0.0.3
github.com/vburenin/ifacemaker v1.2.1
github.com/vektra/mockery/v2 v2.38.0
github.com/weaveworks/goformation/v4 v4.10.2-0.20230526082129-5f5eaa9609b8
github.com/weaveworks/goformation/v4 v4.10.2-0.20231113122203-bf1ae633f95c
github.com/weaveworks/schemer v0.0.0-20230525114451-47139fe25848
github.com/xgfone/netaddr v0.5.1
golang.org/x/crypto v0.16.0
Expand Down
8 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,6 @@ github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnw
github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/awslabs/goformation/v4 v4.15.5/go.mod h1:wB5lKZf1J0MYH1Lt4B9w3opqz0uIjP7MMCAcib3QkwA=
github.com/awslabs/goformation/v4 v4.19.5 h1:Y+Tzh01tWg8gf//AgGKUamaja7Wx9NPiJf1FpZu4/iU=
github.com/awslabs/goformation/v4 v4.19.5/go.mod h1:JoNpnVCBOUtEz9bFxc9sjy8uBUCLF5c4D1L7RhRTVM8=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
Expand Down Expand Up @@ -1325,7 +1324,6 @@ github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
Expand All @@ -1336,7 +1334,6 @@ github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
Expand Down Expand Up @@ -1643,8 +1640,8 @@ github.com/vektra/mockery/v2 v2.38.0 h1:I0LBuUzZHqAU4d1DknW0DTFBPO6n8TaD38WL2KJf
github.com/vektra/mockery/v2 v2.38.0/go.mod h1:diB13hxXG6QrTR0ol2Rk8s2dRMftzvExSvPDKr+IYKk=
github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 h1:txplJASvd6b/hrE0s/Ixfpp2cuwH9IO9oZBAN9iYa4A=
github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2/go.mod h1:DGCIhurYgnLz8J9ga1fMV/fbLDyUvTyrWXVWUIyJon4=
github.com/weaveworks/goformation/v4 v4.10.2-0.20230526082129-5f5eaa9609b8 h1:qjATkrajxxCgWIQaVgOmG8RiRwGGn1mOm6szwVty7zA=
github.com/weaveworks/goformation/v4 v4.10.2-0.20230526082129-5f5eaa9609b8/go.mod h1:x92o12+Azh6DQ4yoXT5oEuE7dhQHR5V2vy/fmZ6pO7k=
github.com/weaveworks/goformation/v4 v4.10.2-0.20231113122203-bf1ae633f95c h1:iejfxgm8iQ6Jr3yT7Javgk40drlL6w9B6+zgACs0fMw=
github.com/weaveworks/goformation/v4 v4.10.2-0.20231113122203-bf1ae633f95c/go.mod h1:3c2tyJmoge5qTS4PXS0niVJxR0YzroIBsts3dQI3EdI=
github.com/weaveworks/schemer v0.0.0-20230525114451-47139fe25848 h1:I7S+IHZIU49skVgTNArf9bIdy07mCn1Z0zv1r07ROws=
github.com/weaveworks/schemer v0.0.0-20230525114451-47139fe25848/go.mod h1:y8Luzq6JDsYVoIV0QAlnvIiq8bSaap0myMjWKyzVFTY=
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I=
Expand Down Expand Up @@ -1862,7 +1859,6 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
Expand Down
122 changes: 117 additions & 5 deletions integration/tests/crud/creategetdelete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import (
"github.com/weaveworks/eksctl/integration/tests"
clusterutils "github.com/weaveworks/eksctl/integration/utilities/cluster"
"github.com/weaveworks/eksctl/integration/utilities/kube"
"github.com/weaveworks/eksctl/pkg/actions/nodegroup"
api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
"github.com/weaveworks/eksctl/pkg/authconfigmap"
"github.com/weaveworks/eksctl/pkg/eks"
"github.com/weaveworks/eksctl/pkg/iam"
iamoidc "github.com/weaveworks/eksctl/pkg/iam/oidc"
Expand Down Expand Up @@ -163,6 +165,14 @@ var _ = SynchronizedBeforeSuite(func() []byte {

var _ = Describe("(Integration) Create, Get, Scale & Delete", func() {

makeClientset := func() *kubernetes.Clientset {
config, err := clientcmd.BuildConfigFromFlags("", params.KubeconfigPath)
ExpectWithOffset(1, err).NotTo(HaveOccurred())
clientset, err := kubernetes.NewForConfig(config)
ExpectWithOffset(1, err).NotTo(HaveOccurred())
return clientset
}

Context("validating cluster setup", func() {
It("should have created an EKS cluster and 6 CloudFormation stacks", func() {
awsConfig := NewConfig(params.Region)
Expand Down Expand Up @@ -846,11 +856,7 @@ var _ = Describe("(Integration) Create, Get, Scale & Delete", func() {
WithStdin(clusterutils.ReaderFromFile(params.ClusterName, params.Region, "testdata/taints-max-pods.yaml"))).To(RunSuccessfully())

By("asserting that both formats for taints are supported")
config, err := clientcmd.BuildConfigFromFlags("", params.KubeconfigPath)
ExpectWithOffset(1, err).NotTo(HaveOccurred())
clientset, err := kubernetes.NewForConfig(config)
ExpectWithOffset(1, err).NotTo(HaveOccurred())

clientset := makeClientset()
nodeListN1 := tests.ListNodes(clientset, taintsNg1)
nodeListN2 := tests.ListNodes(clientset, taintsNg2)

Expand Down Expand Up @@ -1057,6 +1063,112 @@ var _ = Describe("(Integration) Create, Get, Scale & Delete", func() {
})
})

Context("access entries", Serial, func() {
hasAuthIdentity := func(nodeRoleARN string) bool {
auth, err := authconfigmap.NewFromClientSet(makeClientset())
Expect(err).NotTo(HaveOccurred())
identities, err := auth.GetIdentities()
Expect(err).NotTo(HaveOccurred())
for _, id := range identities {
if roleID, ok := id.(iam.RoleIdentity); ok && roleID.RoleARN == nodeRoleARN {
return true
}
}
return false
}

type authMode int
const (
authModeAccessEntry authMode = iota + 1
authModeAWSAuthConfigMap
)

type ngAuthTest struct {
ngName string
createNgArgs []string

expectedAuthMode authMode
expectedCreateNgOutput string
}

DescribeTable("authorising self-managed nodegroups", Serial, func(nt ngAuthTest) {
cmd := params.EksctlCreateNodegroupCmd.
WithArgs(
"--cluster", params.ClusterName,
"--name", nt.ngName,
"--nodes", "1",
"--managed=false",
).WithArgs(nt.createNgArgs...)
session := cmd.Run()
Expect(session.ExitCode()).To(BeZero())
if nt.expectedCreateNgOutput != "" {
Expect(session.Out.Contents()).To(ContainSubstring(nt.expectedCreateNgOutput))
}

DeferCleanup(func() {
cmd := params.EksctlDeleteCmd.WithArgs(
"nodegroup",
"--cluster", params.ClusterName,
"--name", nt.ngName,
)
Expect(cmd).To(RunSuccessfully())
})

cmd = params.EksctlGetCmd.WithArgs(
"nodegroup",
"--cluster", params.ClusterName,
"--name", nt.ngName,
"-o", "json",
)
session = cmd.Run()
Expect(session.ExitCode()).To(BeZero())
var ngSummaries []nodegroup.Summary
Expect(json.Unmarshal(session.Out.Contents(), &ngSummaries)).To(Succeed())
Expect(ngSummaries).To(HaveLen(1))
ngSummary := ngSummaries[0]
Expect(ngSummary.NodeInstanceRoleARN).NotTo(BeEmpty())

By("checking access entries")
cmd = params.EksctlGetCmd.
WithArgs(
"accessentry",
"--cluster", params.ClusterName,
)
session = cmd.Run()
Expect(session.ExitCode()).To(BeZero())
if nt.expectedAuthMode == authModeAccessEntry {
Expect(session.Out.Contents()).To(ContainSubstring(ngSummary.NodeInstanceRoleARN), "failed to find access entry for nodegroup")
} else {
Expect(session.Out.Contents()).NotTo(ContainSubstring(ngSummary.NodeInstanceRoleARN), "found access entry for nodegroup")
}

By("checking aws-auth ConfigMap")
Expect(hasAuthIdentity(ngSummary.NodeInstanceRoleARN)).To(Equal(nt.expectedAuthMode == authModeAWSAuthConfigMap))
},
Entry("with access entry", ngAuthTest{
ngName: "ng-access-entry",
expectedAuthMode: authModeAccessEntry,
}),

Entry("with --update-auth-configmap", ngAuthTest{
ngName: "ng-update-auth",
createNgArgs: []string{"--update-auth-configmap"},

expectedCreateNgOutput: "--update-auth-configmap is deprecated and will be removed soon; the recommended way " +
"to authorize nodes is by creating EKS access entries",
expectedAuthMode: authModeAWSAuthConfigMap,
}),

Entry("with --update-auth-configmap=false", ngAuthTest{
ngName: "ng-update-auth-false",
createNgArgs: []string{"--update-auth-configmap=false"},

expectedCreateNgOutput: "--update-auth-configmap is deprecated and will be removed soon; eksctl now uses " +
"EKS Access Entries to authorize nodes if it is enabled on the cluster",
}),
)
})

Context("draining nodegroup(s)", func() {
It("should be able to drain a nodegroup", func() {
Expect(params.EksctlDrainNodeGroupCmd.WithArgs(
Expand Down
69 changes: 69 additions & 0 deletions pkg/accessentry/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package accessentry

import (
"fmt"

"github.com/kris-nova/logger"

ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types"

api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
)

// Service is a service for access entries.
type Service struct {
// ClusterStateGetter returns the cluster state.
ClusterStateGetter
}

// ClusterStateGetter returns the cluster state.
type ClusterStateGetter interface {
GetClusterState() *ekstypes.Cluster
}

// IsEnabled reports whether the cluster has access entries enabled.
func (s *Service) IsEnabled() bool {
cluster := s.GetClusterState()
return cluster.AccessConfig != nil && IsEnabled(cluster.AccessConfig.AuthenticationMode)
}

// IsAWSAuthDisabled reports whether the cluster has authentication mode set to API.
func (s *Service) IsAWSAuthDisabled() bool {
accessConfig := s.GetClusterState().AccessConfig
return accessConfig == nil || accessConfig.AuthenticationMode == ekstypes.AuthenticationModeApi
}

// IsEnabled reports whether the authenticationMode indicates that the cluster has access entries enabled.
func IsEnabled(authenticationMode ekstypes.AuthenticationMode) bool {
return authenticationMode != ekstypes.AuthenticationModeConfigMap
}

// ValidateAPIServerAccess validates whether the API server is accessible for clusterConfig, and logs warning messages
// for operations that might fail later.
func ValidateAPIServerAccess(clusterConfig *api.ClusterConfig) error {
if !api.IsDisabled(clusterConfig.AccessConfig.BootstrapClusterCreatorAdminPermissions) {
return nil
}

const (
apiServerConnectivityMsg = "eksctl features that require connectivity to the Kubernetes API server will fail"
bootstrapFalseMsg = "bootstrapClusterCreatorAdminPermissions is false"
)
switch clusterConfig.AccessConfig.AuthenticationMode {
case ekstypes.AuthenticationModeConfigMap:
if len(clusterConfig.NodeGroups) > 0 {
return fmt.Errorf("cannot create self-managed nodegroups when authenticationMode is %s and %s", ekstypes.AuthenticationModeConfigMap, bootstrapFalseMsg)
}
logger.Warning("%s; %s", bootstrapFalseMsg, apiServerConnectivityMsg)
default:
if len(clusterConfig.AccessConfig.AccessEntries) == 0 {
if len(clusterConfig.NodeGroups) > 0 {
return fmt.Errorf("cannot create self-managed nodegroups when %s and no access entries are configured", bootstrapFalseMsg)
}
logger.Warning("%s and no access entries are configured; %s", bootstrapFalseMsg, apiServerConnectivityMsg)
return nil
}
logger.Warning("%s; if no configured access entries allow access to the Kubernetes API server, %s", bootstrapFalseMsg, apiServerConnectivityMsg)
}
return nil
}
13 changes: 13 additions & 0 deletions pkg/actions/accessentry/access_entry_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package accessentry_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestAccessEntry(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Access Entry Suite")
}
66 changes: 66 additions & 0 deletions pkg/actions/accessentry/creator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package accessentry

import (
"context"
"crypto/sha1"
"encoding/base32"
"errors"
"fmt"
"strings"

api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
"github.com/weaveworks/eksctl/pkg/utils/tasks"
)

// CreatorInterface creates access entries.
//
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate -o fakes/fake_creator.go . CreatorInterface
type CreatorInterface interface {
// Create creates access entries.
Create(ctx context.Context, accessEntries []api.AccessEntry) error
// CreateTasks creates a TaskTree for creating access entries.
CreateTasks(ctx context.Context, accessEntries []api.AccessEntry) *tasks.TaskTree
}

// A Creator creates access entries.
type Creator struct {
ClusterName string
StackCreator StackCreator
}

// Create creates the specified access entries.
func (m *Creator) Create(ctx context.Context, accessEntries []api.AccessEntry) error {
taskTree := m.CreateTasks(ctx, accessEntries)
if errs := taskTree.DoAllSync(); len(errs) > 0 {
var allErrs []string
for _, err := range errs {
allErrs = append(allErrs, err.Error())
}
return errors.New(strings.Join(allErrs, "\n"))
}
return nil
}

// CreateTasks creates a TaskTree for creating access entries.
func (m *Creator) CreateTasks(ctx context.Context, accessEntries []api.AccessEntry) *tasks.TaskTree {
taskTree := &tasks.TaskTree{
Parallel: true,
}
for _, ae := range accessEntries {
taskTree.Append(&accessEntryTask{
ctx: ctx,
info: fmt.Sprintf("create access entry for principal ARN %s", ae.PrincipalARN),
clusterName: m.ClusterName,
accessEntry: ae,
stackCreator: m.StackCreator,
})
}
return taskTree
}

// MakeStackName creates a stack name for the specified access entry.
func MakeStackName(clusterName string, accessEntry api.AccessEntry) string {
s := sha1.Sum([]byte(accessEntry.PrincipalARN.String()))
return fmt.Sprintf("eksctl-%s-accessentry-%s", clusterName, base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(s[:]))
}
Loading