From 563dfda1eaa409357b8120125c3d2fa51e910733 Mon Sep 17 00:00:00 2001 From: Robert Bo Davis Date: Wed, 28 Nov 2018 12:52:07 -0500 Subject: [PATCH] Add support for EKS cluster authentication. --- DOCS.md | 61 +++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 3 +++ kubeconfig | 18 +++++++++++-- main.go | 12 +++++++++ plugin/plugin.go | 8 ++++-- plugin/plugin_test.go | 42 +++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 4 deletions(-) diff --git a/DOCS.md b/DOCS.md index 4be9957..0184396 100644 --- a/DOCS.md +++ b/DOCS.md @@ -187,6 +187,67 @@ Get the token for the default service account in the default namespace: KUBERNETES_TOKEN=$(kubectl get secret $(kubectl get sa default -o jsonpath='{.secrets[].name}{"\n"}') -o jsonpath="{.data.token}" | base64 -D) ``` +## Deploying to EKS + +To deploy to EKS, you should have `api_server` and `kubernetes_certificate` secrets set in drone. If drone is deploying from outside of AWS, you should also have an `aws_access_key_id`, `aws_secret_access_key` secret. An `AWS_DEFAULT_REGION` environmental variable should also be set for the deployment. + +### Use the AWS IAM keys for the user who created the EKS cluster (not recommended): + +```YAML +pipeline + helm_deploy: + image: quay.io/ipedrazas/drone-helm + chart: ./charts/my-chart + release: ${DRONE_BRANCH} + values: image.tag=${DRONE_BRANCH}-${DRONE_COMMIT_SHA:0:7} + prefix: STAGING + namespace: staging + eks_cluster: my-eks-cluster + environment: + - AWS_DEFAULT_REGION=us-east-1 + + secrets: [ aws_access_key_id, aws_secret_access_key, api_server, kubernetes_certificate ] +``` + +### Use role based EKS access +You must first [configure an IAM Role for cluster access](https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html) and configure policy to allow an iam user or iam role to assume the new role. + +Running drone agent on an ec2 instance that has Role based access to assume the cluster access Role created above: +```YAML +pipeline + helm_deploy: + image: quay.io/ipedrazas/drone-helm + chart: ./charts/my-chart + release: ${DRONE_BRANCH} + values: image.tag=${DRONE_BRANCH}-${DRONE_COMMIT_SHA:0:7} + prefix: STAGING + namespace: staging + eks_cluster: my-eks-cluster + eks_role_arn: arn:aws:iam::[ACCOUNT ID HERE]:role/eks-master + environment: + - AWS_DEFAULT_REGION=us-east-1 + + secrets: [ api_server, kubernetes_certificate ] +``` + +Using IAM keys with access to assume the cluster access Role created above: +```YAML +pipeline + helm_deploy: + image: quay.io/ipedrazas/drone-helm + chart: ./charts/my-chart + release: ${DRONE_BRANCH} + values: image.tag=${DRONE_BRANCH}-${DRONE_COMMIT_SHA:0:7} + prefix: STAGING + namespace: staging + eks_cluster: my-eks-cluster + eks_role_arn: arn:aws:iam::[ACCOUNT ID HERE]:role/eks-master + environment: + - AWS_DEFAULT_REGION=us-east-1 + + secrets: [ aws_access_key_id, aws_secret_access_key, api_server, kubernetes_certificate ] +``` + ## Advanced customisations and debugging This plugin installs [Tiller](https://github.com/kubernetes/helm/blob/master/docs/architecture.md) in the cluster, if you want to specify the namespace where `tiller` ins installed, use the `tiller_ns` attribute. diff --git a/Dockerfile b/Dockerfile index e74e347..7f9ccd5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,10 +17,13 @@ RUN set -ex \ && apk add --no-cache curl ca-certificates \ && curl -o /tmp/${FILENAME} http://storage.googleapis.com/kubernetes-helm/${FILENAME} \ && curl -o /tmp/kubectl https://storage.googleapis.com/kubernetes-release/release/${KUBECTL}/bin/linux/amd64/kubectl \ + && curl -o /tmp/aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-07-26/bin/linux/amd64/aws-iam-authenticator \ && tar -zxvf /tmp/${FILENAME} -C /tmp \ && mv /tmp/linux-amd64/helm /bin/helm \ && chmod +x /tmp/kubectl \ && mv /tmp/kubectl /bin/kubectl \ + && chmod +x /tmp/aws-iam-authenticator \ + && mv /tmp/aws-iam-authenticator /bin/aws-iam-authenticator \ && rm -rf /tmp/* LABEL description="Kubectl and Helm." diff --git a/kubeconfig b/kubeconfig index 0c88acb..f5f85da 100644 --- a/kubeconfig +++ b/kubeconfig @@ -14,7 +14,7 @@ contexts: cluster: helm {{ if .Namespace }} namespace: {{ .Namespace }} -{{ end}} +{{ end }} user: {{ .ServiceAccount }} name: helm current-context: "helm" @@ -23,4 +23,18 @@ preferences: {} users: - name: {{ .ServiceAccount }} user: - token: {{ .Token }} \ No newline at end of file +{{ if .Token }} + token: {{ .Token }} +{{ else if .EKSCluster }} + exec: + apiVersion: client.authentication.k8s.io/v1alpha1 + command: aws-iam-authenticator + args: + - "token" + - "-i" + - "{{ .EKSCluster }}" + {{ if .EKSRoleARN }} + - "-r" + - "{{ .EKSRoleARN }}" + {{ end }} +{{ end }} \ No newline at end of file diff --git a/main.go b/main.go index d3126f0..39ecab3 100644 --- a/main.go +++ b/main.go @@ -58,6 +58,16 @@ func main() { Usage: "specify the exact chart version to use. If this is not specified, the latest version is used", EnvVar: "PLUGIN_CHART_VERSION,CHART_VERSION", }, + cli.StringFlag{ + Name: "eks_cluster", + Usage: "Name of EKS cluster. Requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION secrets AND/OR EKS_ROLE and proper role configuration", + EnvVar: "PLUGIN_EKS_CLUSTER,EKS_CLUSTER", + }, + cli.StringFlag{ + Name: "eks_role_arn", + Usage: "ARN of EKS role to assume for EKS authentication.", + EnvVar: "PLUGIN_EKS_ROLE_ARN,EKS_ROLE_ARN", + }, cli.StringFlag{ Name: "values", Usage: "Kubernetes helm release", @@ -175,6 +185,8 @@ func run(c *cli.Context) error { HelmRepos: c.StringSlice("helm_repos"), Chart: c.String("chart"), Version: c.String("chart-version"), + EKSCluster: c.String("eks_cluster"), + EKSRoleARN: c.String("eks_role_arn"), Debug: c.Bool("debug"), DryRun: c.Bool("dry-run"), Secrets: c.StringSlice("secrets"), diff --git a/plugin/plugin.go b/plugin/plugin.go index 9ca5963..2f4b72e 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -30,6 +30,8 @@ type ( Release string `json:"release"` Chart string `json:"chart"` Version string `json:"version"` + EKSCluster string `json:"eks_cluster"` + EKSRoleARN string `json:"eks_role_arn"` Values string `json:"values"` StringValues string `json:"string_values"` ValuesFiles string `json:"values_files"` @@ -234,8 +236,10 @@ func (p *Plugin) Exec() error { if p.Config.APIServer == "" { return fmt.Errorf("Error: API Server is needed to deploy.") } - if p.Config.Token == "" { - return fmt.Errorf("Error: Token is needed to deploy.") + if p.Config.EKSCluster == "" { + if p.Config.Token == "" { + return fmt.Errorf("Error: Token is needed to deploy.") + } } initialiseKubeconfig(&p.Config, KUBECONFIG, p.Config.KubeConfig) } diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index 0e0c8ce..4f61a68 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -42,6 +42,48 @@ func TestInitialiseKubeconfig(t *testing.T) { if !strings.Contains(kubeConfigStr, "my-cert-data") { t.Errorf("Kubeconfig doesn't render certificate") } + if strings.Contains(kubeConfigStr, "aws-iam-authenticator") { + t.Errorf("Kubeconfig renders EKS cluster configuration") + } +} + +func TestInitialiseKubeconfigEKS(t *testing.T) { + + plugin := Plugin{ + Config: Config{ + APIServer: "http://myapiserver", + Certificate: "my-cert-data", + EKSCluster: "my-eks-cluster-name", + EKSRoleARN: "my-eks-role-arn", + HelmCommand: "", + Namespace: "default", + SkipTLSVerify: false, // if set the true with Certificate, this test will fail + }, + } + + configfile := "config3.test" + initialiseKubeconfig(&plugin.Config, "../kubeconfig", configfile) + data, err := ioutil.ReadFile(configfile) + if err != nil { + t.Errorf("Error reading file %v", err) + } + kubeConfigStr := string(data) + + if strings.Contains(kubeConfigStr, "token:") { + t.Errorf("Kubeconfig renders token") + } + if !strings.Contains(kubeConfigStr, "http://myapiserver") { + t.Errorf("Kubeconfig doesn't render APIServer") + } + if !strings.Contains(kubeConfigStr, "my-cert-data") { + t.Errorf("Kubeconfig doesn't render certificate") + } + if !strings.Contains(kubeConfigStr, "my-eks-cluster-name") { + t.Errorf("Kubeconfig doesn't render EKS cluster name") + } + if !strings.Contains(kubeConfigStr, "my-eks-role-arn") { + t.Errorf("Kubeconfig doesn't render EKS role ARN") + } } func TestGetHelmCommandEmptyPushEvent(t *testing.T) {