Skip to content

Commit

Permalink
Adds more robust testing for ebs csi drivers
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonhawkharris committed Feb 22, 2023
1 parent efe0e28 commit 6412338
Show file tree
Hide file tree
Showing 8 changed files with 464 additions and 196 deletions.
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@ require (
github.com/Masterminds/sprig v2.15.0+incompatible // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/aokoli/goutils v1.0.1 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.13 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 // indirect
github.com/aws/aws-sdk-go-v2/service/ec2 v1.86.0 // indirect
github.com/aws/aws-sdk-go-v2/service/iam v1.19.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.2 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY=
github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.17.5 h1:TzCUW1Nq4H8Xscph5M/skINUitxM5UBAyvm2s7XBzL4=
github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/config v1.18.13 h1:v0xlYqbO6/EVlM8tUn2QEOA7btQxcgidEq2JRDBPTho=
github.com/aws/aws-sdk-go-v2/config v1.18.13/go.mod h1:r39wGSZB7wPDW1i54JyQXUpc5KsWjh5z/3S5D9eCqDg=
github.com/aws/aws-sdk-go-v2/credentials v1.13.13 h1:zw1KAc1kl00NYd3ofVmFrb09qnYlSQMeh+fmlQRAihI=
Expand All @@ -41,14 +43,20 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 h1:3aMfcTmoXtTZnaT86QlVaY
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22/go.mod h1:YGSIJyQ6D6FjKMQh16hVFSIUD54L4F7zTGePqYMYYJU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 h1:r+XwaCLpIvCKjBIYy/HVZujQS9tsz5ohHG3ZIe0wKoE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 h1:9/aKwwus0TQxppPXFmf010DFrE+ssSbzroLVYINA+xE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29/go.mod h1:Dip3sIGv485+xerzVv24emnjX5Sg88utCL8fwGmCeWg=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 h1:7AwGYXDdqRQYsluvKFmWoqpcOQJ4bH634SkYf3FNj/A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22/go.mod h1:EqK7gVrIGAHyZItrD1D8B0ilgwMD1GiWAmbU4u/JHNk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 h1:b/Vn141DBuLVgXbhRWIrl9g+ww7G+ScV5SzniWR13jQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23/go.mod h1:mr6c4cHC+S/MMkrjtSlG4QA36kOznDep+0fga5L/fGQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 h1:J4xhFd6zHhdF9jPP0FQJ6WknzBboGMBNjKOv4iTuw4A=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29/go.mod h1:TwuqRBGzxjQJIwH16/fOZodwXt2Zxa9/cwJC5ke4j7s=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.86.0 h1:4dt0Mg5veHbMLcA2JAR9LDxvqXjtG0ZLQdxZG/2wHy4=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.86.0/go.mod h1:jK4MhMMe6HIe4qnjGaQqQQECcsxRZ0q86oCq06T8IEE=
github.com/aws/aws-sdk-go-v2/service/eks v1.27.3 h1:jlh0AJVhauqSGaiwRJx4j0LNBGEAaGn46sA1XJyTj0E=
github.com/aws/aws-sdk-go-v2/service/eks v1.27.3/go.mod h1:S30WtE6uWErcppqG9Rl03MATEFYsm0vnDyxPUKmmCqU=
github.com/aws/aws-sdk-go-v2/service/iam v1.19.3 h1:vaDXu/p/5RNK++B2pqKS3hwsWGxsVjJJqPaAMg0OkiM=
github.com/aws/aws-sdk-go-v2/service/iam v1.19.3/go.mod h1:F5Xt96+AfAiyMpRXHy9CKafE/KULVwj7MwgZ0a4row4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 h1:LjFQf8hFuMO22HkV5VWGLBvmCLBCLPivUAmpdpnp4Vs=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22/go.mod h1:xt0Au8yPIwYXf/GYPy/vl4K3CgwhfQMYbrH7DlUUIws=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.2 h1:EN102fWY7hI5u/2FPheTrwwMHkSXfl49RYkeEnJsrCU=
Expand Down
273 changes: 273 additions & 0 deletions internal/validate/kube/eks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package kube

import (
"context"
"fmt"
"path/filepath"
"reflect"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"

"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/eks"
"github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/sourcegraph/src-cli/internal/validate"

"github.com/sourcegraph/sourcegraph/lib/errors"
)

type EbsTestObjects struct {
addons []string
serviceAccount string
ebsRolePolicy RolePolicy
}

// EksEbsCsiDrivers will validate that EKS cluster has ebs-cli drivers installed and configured
func EksEbsCsiDrivers(ctx context.Context, config *Config) ([]validate.Result, error) {
var results []validate.Result
var ebsTestParams EbsTestObjects

addons, err := getAddons(ctx, config.eksClient)
if err != nil {
results = append(results, validate.Result{
Status: validate.Failure,
Message: "EKS: could not validate ebs in addons",
})
return results, err
}

ebsSA, err := getEbsSA(ctx, config.clientSet)
if err != nil {
results = append(results, validate.Result{
Status: validate.Failure,
Message: "EKS: could not validate ebs service account",
})
return results, err
}

EBSCSIRole, err := getEBSCSIRole(ctx, config.iamClient, ebsSA)
if err != nil {
results = append(results, validate.Result{
Status: validate.Failure,
Message: "EKS: could not validate ebs role policy",
})
}

ebsTestParams.addons = addons
ebsTestParams.serviceAccount = ebsSA
ebsTestParams.ebsRolePolicy = EBSCSIRole

r := validateEbsCsiDrivers(ebsTestParams)
results = append(results, r...)

return results, nil
}

func validateEbsCsiDrivers(testers EbsTestObjects) (result []validate.Result) {
for _, addon := range testers.addons {
if addon == "aws-ebs-csi-driver" {
result = append(result, validate.Result{
Status: validate.Success,
Message: "EKS: ebs-csi driver validated",
})
} else {
result = append(result, validate.Result{
Status: validate.Failure,
Message: "EKS: no 'aws-ebs-csi-driver' present in addons",
})
}
}

if testers.serviceAccount != "" {
result = append(result, validate.Result{
Status: validate.Success,
Message: "EKS: ebs service account validated",
})
} else {
result = append(result, validate.Result{
Status: validate.Failure,
Message: "EKS: no 'ebs-csi-controller-sa' service account present in cluster",
})
}

if *testers.ebsRolePolicy.PolicyName == "AmazonEBSCSIDriverPolicy" {
result = append(result, validate.Result{
Status: validate.Success,
Message: "EKS: service account ebs role policy validated",
})
} else {
result = append(result, validate.Result{
Status: validate.Failure,
Message: "EKS: no 'AmazonEBSCSIDriverPolicy' attached to role",
})
}

return result
}

// EksVpc checks if a valid vpc available
func EksVpc(ctx context.Context, config *Config) ([]validate.Result, error) {
var results []validate.Result
if config.ec2Client == nil {
results = append(results, validate.Result{
Status: validate.Failure,
Message: "EKS: validate VPC failed",
})
}

inputs := &ec2.DescribeVpcsInput{}
outputs, err := config.ec2Client.DescribeVpcs(ctx, inputs)

if err != nil {
results = append(results, validate.Result{
Status: validate.Failure,
Message: "EKS: Validate VPC failed",
})
return results, nil
}

if len(outputs.Vpcs) == 0 {
results = append(results, validate.Result{
Status: validate.Failure,
Message: "EKS: Validate VPC failed: No VPC configured",
})
return results, nil
}

for _, vpc := range outputs.Vpcs {
r := validateVpc(&vpc)
results = append(results, r...)
}

return results, nil
}

func validateVpc(vpc *types.Vpc) (result []validate.Result) {
state := vpc.State

if state == "available" {
result = append(result, validate.Result{
Status: validate.Success,
Message: "VPC is validated",
})
return result
}

result = append(result, validate.Result{
Status: validate.Failure,
Message: "vpc.State stuck in pending state",
})

return result
}

// checks if current context is set to EKS cluster
func CurrentContextSetToEKSCluster() error {
home := homedir.HomeDir()
pathToKubeConfig := filepath.Join(home, ".kube", "config")

config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: pathToKubeConfig},
&clientcmd.ConfigOverrides{
CurrentContext: "",
}).RawConfig()

if err != nil {
return err
}

got := strings.Split(config.CurrentContext, ":")
want := []string{"arn", "aws", "eks"}

if len(got) >= 3 {
got = got[:3]
if reflect.DeepEqual(got, want) {
return nil
}
}

return errors.Newf("%s no eks cluster configured", validate.FailureEmoji)
}

func getAddons(ctx context.Context, client *eks.Client) ([]string, error) {
clusterName := getClusterName(ctx, client)
inputs := &eks.ListAddonsInput{ClusterName: clusterName}
outputs, err := client.ListAddons(ctx, inputs)

if err != nil {
return nil, err
}

return outputs.Addons, nil
}

func getEbsSA(ctx context.Context, client *kubernetes.Clientset) (string, error) {
serviceAccounts := client.CoreV1().ServiceAccounts("kube-system")
ebsSA, err := serviceAccounts.Get(
ctx,
"ebs-csi-controller-sa",
metav1.GetOptions{},
)

if err != nil {
return "", err
}

annotations := ebsSA.GetAnnotations()
roleArn := strings.Split(annotations["eks.amazonaws.com/role-arn"], "/")
ebsControllerSA := roleArn[len(roleArn)-1]

return ebsControllerSA, nil
}

type RolePolicy struct {
PolicyName *string
PolicyArn *string
}

func getEBSCSIRole(ctx context.Context, client *iam.Client, SAName string) (RolePolicy, error) {
inputs := iam.ListAttachedRolePoliciesInput{RoleName: &SAName}
outputs, err := client.ListAttachedRolePolicies(ctx, &inputs)

if err != nil {
return RolePolicy{}, err
}

var policyName *string
for _, policy := range outputs.AttachedPolicies {
policyName = policy.PolicyName
if *policyName == "AmazonEBSCSIDriverPolicy" {
return RolePolicy{
PolicyName: policy.PolicyName,
PolicyArn: policy.PolicyArn,
}, nil
}
}

return RolePolicy{}, nil
}

func getClusterName(ctx context.Context, client *eks.Client) *string {
home := homedir.HomeDir()
pathToKubeConfig := filepath.Join(home, ".kube", "config")

config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: pathToKubeConfig},
&clientcmd.ConfigOverrides{
CurrentContext: "",
}).RawConfig()

if err != nil {
fmt.Printf("error while checking current context: %s\n", err)
return nil
}

currentContext := strings.Split(config.CurrentContext, "/")
clusterName := currentContext[len(currentContext)-1]
return &clusterName
}
Loading

0 comments on commit 6412338

Please sign in to comment.