Skip to content

Commit

Permalink
Merge pull request gruntwork-io#73 from gruntwork-io/yori-update-perm…
Browse files Browse the repository at this point in the history
…issions

Grant ability to get the Tiller Deployment
  • Loading branch information
yorinasub17 authored Oct 9, 2019
2 parents 09d4120 + 6340a9b commit ec1f2ab
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 33 deletions.
4 changes: 3 additions & 1 deletion cmd/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ At least one of --%s, --%s, or --%s are required.`, RbacUserFlag, RbacGroupFlag,
tlsAlgorithmFlag,
tlsECDSACurveFlag,
tlsRSABitsFlag,
tillerDeploymentNameFlag,
helmKubectlContextNameFlag,
helmKubeconfigFlag,
helmKubectlServerFlag,
Expand Down Expand Up @@ -618,13 +619,14 @@ func grantHelmAccess(cliContext *cli.Context) error {
if err != nil {
return err
}
tillerDeploymentName := cliContext.String(tillerDeploymentNameFlag.Name)
rbacGroups := cliContext.StringSlice(grantedRbacGroupsFlag.Name)
rbacUsers := cliContext.StringSlice(grantedRbacUsersFlag.Name)
serviceAccounts := cliContext.StringSlice(grantedServiceAccountsFlag.Name)
if len(rbacGroups) == 0 && len(rbacUsers) == 0 && len(serviceAccounts) == 0 {
return entrypoint.NewRequiredArgsError(fmt.Sprintf("At least one --%s, --%s, or --%s is required", RbacUserFlag, RbacGroupFlag, RbacServiceAccountFlag))
}
return helm.GrantAccess(kubectlOptions, tlsOptions, tillerNamespace, rbacGroups, rbacUsers, serviceAccounts)
return helm.GrantAccess(kubectlOptions, tlsOptions, tillerDeploymentName, tillerNamespace, rbacGroups, rbacUsers, serviceAccounts)
}

// revokeHelmAccess is the action function for the helm revoke command.
Expand Down
2 changes: 1 addition & 1 deletion helm/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func grantAndConfigureLocalClient(
return errors.WithStackTrace(UnknownRBACEntityType{localClientRBACEntity.EntityType()})
}

err := GrantAccess(kubectlOptions, tlsOptions, tillerNamespace, rbacGroups, rbacUsers, rbacServiceAccounts)
err := GrantAccess(kubectlOptions, tlsOptions, TillerDeploymentName, tillerNamespace, rbacGroups, rbacUsers, rbacServiceAccounts)
if err != nil {
return err
}
Expand Down
75 changes: 51 additions & 24 deletions helm/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestValidateRequiredResourcesForDeploy(t *testing.T) {
func TestHelmDeployConfigureUndeploy(t *testing.T) {
t.Parallel()

imageSpec := "gcr.io/kubernetes-helm/tiller:v2.14.0"
imageSpec := "gcr.io/kubernetes-helm/tiller:v2.14.3"

kubectlOptions := kubectl.GetTestKubectlOptions(t)
terratestKubectlOptions := k8s.NewKubectlOptions("", "")
Expand All @@ -80,32 +80,40 @@ func TestHelmDeployConfigureUndeploy(t *testing.T) {
tlsOptions := tls.SampleTlsOptions(tls.ECDSAAlgorithm)
clientTLSOptions := tls.SampleTlsOptions(tls.ECDSAAlgorithm)
clientTLSOptions.DistinguishedName.CommonName = "client"
namespaceName := strings.ToLower(random.UniqueId())
serviceAccountName := fmt.Sprintf("%s-service-account", namespaceName)
tillerNamespaceName := strings.ToLower(random.UniqueId())
resourceNamespaceName := strings.ToLower(random.UniqueId())
serviceAccountName := fmt.Sprintf("%s-service-account", tillerNamespaceName)

defer k8s.DeleteNamespace(t, terratestKubectlOptions, namespaceName)
k8s.CreateNamespace(t, terratestKubectlOptions, namespaceName)
terratestKubectlOptions.Namespace = namespaceName
defer k8s.DeleteNamespace(t, terratestKubectlOptions, tillerNamespaceName)
k8s.CreateNamespace(t, terratestKubectlOptions, tillerNamespaceName)
defer k8s.DeleteNamespace(t, terratestKubectlOptions, resourceNamespaceName)
k8s.CreateNamespace(t, terratestKubectlOptions, resourceNamespaceName)

// Create a test service account we can use for auth
// Create a test service account we can use for auth in the resource namespace
terratestKubectlOptions.Namespace = resourceNamespaceName
testServiceAccountName, testServiceAccountKubectlOptions := createServiceAccountForAuth(t, terratestKubectlOptions)
defer k8s.DeleteConfigContextE(t, testServiceAccountKubectlOptions.ContextName)
testServiceAccountInfo := ServiceAccountInfo{Name: testServiceAccountName, Namespace: terratestKubectlOptions.Namespace}

// Create a service account for Tiller
terratestKubectlOptions.Namespace = tillerNamespaceName
k8s.CreateServiceAccount(t, terratestKubectlOptions, serviceAccountName)
bindNamespaceAdminRole(t, terratestKubectlOptions, serviceAccountName)
bindNamespaceAdminRole(t, terratestKubectlOptions, tillerNamespaceName, serviceAccountName)

// Also make sure to bind admin roles for resource namespace
terratestKubectlOptions.Namespace = resourceNamespaceName
bindNamespaceAdminRole(t, terratestKubectlOptions, tillerNamespaceName, serviceAccountName)

defer func() {
// Make sure to undeploy all helm releases before undeploying the server. However, don't force undeploy the
// server so that it crashes should the release removal fail.
assert.NoError(t, Undeploy(kubectlOptions, namespaceName, "", false, true))
assert.NoError(t, Undeploy(kubectlOptions, tillerNamespaceName, "", false, true))
}()
// Deploy, Grant, and Configure
assert.NoError(t, Deploy(
kubectlOptions,
namespaceName,
namespaceName,
tillerNamespaceName,
resourceNamespaceName,
serviceAccountName,
tlsOptions,
clientTLSOptions,
Expand All @@ -115,10 +123,11 @@ func TestHelmDeployConfigureUndeploy(t *testing.T) {
))

// Check tiller pod is in chosen namespace
terratestKubectlOptions.Namespace = tillerNamespaceName
tillerPodName := validateTillerPodDeployedInNamespace(t, terratestKubectlOptions)

// Check tiller pod is using the right image
validateTillerPodImage(t, terratestKubectlOptions, namespaceName, imageSpec)
validateTillerPodImage(t, terratestKubectlOptions, tillerNamespaceName, imageSpec)

// Check tiller pod is launched with the right service account
validateTillerPodServiceAccount(t, terratestKubectlOptions, tillerPodName, serviceAccountName)
Expand All @@ -133,29 +142,32 @@ func TestHelmDeployConfigureUndeploy(t *testing.T) {
validateTillerAndClientTLSDifferent(t, terratestKubectlOptions, testServiceAccountInfo)

// Check that we can deploy a helm chart
validateHelmChartDeploy(t, testServiceAccountKubectlOptions, namespaceName)
validateHelmChartDeploy(t, testServiceAccountKubectlOptions, tillerNamespaceName, resourceNamespaceName)

// Check that the test service account can get the tiller deployment resource
validateGetTillerDeployment(t, testServiceAccountKubectlOptions, tillerNamespaceName)

// Check that the rendered helm env file works
validateHelmEnvFile(t, testServiceAccountKubectlOptions)

// Revoke the tiller service account
// Revoke the test service account
rbacGroups := []string{}
rbacUsers := []string{}
serviceAccounts := []string{fmt.Sprintf("%s/%s", namespaceName, testServiceAccountName)}
require.NoError(t, RevokeAccess(kubectlOptions, namespaceName, rbacGroups, rbacUsers, serviceAccounts))
serviceAccounts := []string{fmt.Sprintf("%s/%s", resourceNamespaceName, testServiceAccountName)}
require.NoError(t, RevokeAccess(kubectlOptions, tillerNamespaceName, rbacGroups, rbacUsers, serviceAccounts))

// ServiceAccount role has been removed
err = validateNoRole(t, kubeClient, namespaceName, testServiceAccountName)
roleName := getTillerAccessRoleName(testServiceAccountName, namespaceName)
err = validateNoRole(t, kubeClient, tillerNamespaceName, testServiceAccountName)
roleName := getTillerAccessRoleName(testServiceAccountName, tillerNamespaceName)
assert.Equal(t, err.Error(), fmt.Sprintf("roles.rbac.authorization.k8s.io \"%s\" not found", roleName))

// ServiceAccount role binding has been removed
err = validateNoRoleBinding(t, kubeClient, namespaceName, testServiceAccountName)
err = validateNoRoleBinding(t, kubeClient, tillerNamespaceName, testServiceAccountName)
roleBindingName := getTillerAccessRoleBindingName(testServiceAccountName, roleName)
assert.Equal(t, err.Error(), fmt.Sprintf("rolebindings.rbac.authorization.k8s.io \"%s\" not found", roleBindingName))

// ServiceAccount TLS secret has been removed
err = validateNoTLSSecret(t, kubeClient, namespaceName, serviceAccountName)
err = validateNoTLSSecret(t, kubeClient, tillerNamespaceName, serviceAccountName)
secretName := getTillerClientCertSecretName(serviceAccountName)
assert.Equal(t, err.Error(), fmt.Sprintf("secrets \"%s\" not found", secretName))
}
Expand Down Expand Up @@ -237,6 +249,7 @@ func validateGenerateCertificateKeyPair(t *testing.T, algorithm string) {
algorithm,
tmpDir,
)
require.NoError(t, err)

// Make sure the keys are compatible with cert
validateKeyCompatibility(t, caCertificateKeyPair)
Expand Down Expand Up @@ -279,7 +292,12 @@ func validateKeyCompatibility(t *testing.T, certKeyPair tls.CertificateKeyPairPa
}

// validateHelmChartDeploy checks if we can deploy a simple helm chart to the server.
func validateHelmChartDeploy(t *testing.T, kubectlOptions *kubectl.KubectlOptions, namespace string) {
func validateHelmChartDeploy(
t *testing.T,
kubectlOptions *kubectl.KubectlOptions,
tillerNamespace string,
resourceNamespace string,
) {
require.NoError(
t,
RunHelm(
Expand All @@ -290,13 +308,22 @@ func validateHelmChartDeploy(t *testing.T, kubectlOptions *kubectl.KubectlOption
"--tls",
"--tls-verify",
"--tiller-namespace",
namespace,
tillerNamespace,
"--namespace",
namespace,
resourceNamespace,
),
)
}

// validateGetTillerDeployment verifies that the provided account has access to read the tiller deployment resource.
func validateGetTillerDeployment(t *testing.T, options *kubectl.KubectlOptions, tillerNamespace string) {
tillerDeploymentName := "tiller-deploy"
client, err := kubectl.GetKubernetesClientFromOptions(options)
require.NoError(t, err)
_, err = client.AppsV1().Deployments(tillerNamespace).Get(tillerDeploymentName, metav1.GetOptions{})
require.NoError(t, err)
}

// validateHelmEnvFile sources the generated helm env file and verifies it sets the necessary and sufficient
// environment variables for helm to talk to the deployed Tiller instance.
func validateHelmEnvFile(t *testing.T, options *kubectl.KubectlOptions) {
Expand All @@ -323,7 +350,7 @@ func validateHelmEnvFile(t *testing.T, options *kubectl.KubectlOptions) {
}

// validateServiceAccountRoleRemoved
func validateNoRole(t *testing.T, client *kubernetes.Clientset, namespace, serviceAccountName string) error {
func validateNoRole(t *testing.T, client *kubernetes.Clientset, namespace string, serviceAccountName string) error {
roleName := getTillerAccessRoleName(serviceAccountName, namespace)
_, err := client.RbacV1().Roles(namespace).Get(roleName, metav1.GetOptions{})
return err
Expand Down
20 changes: 15 additions & 5 deletions helm/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
func GrantAccess(
kubectlOptions *kubectl.KubectlOptions,
tlsOptions tls.TLSOptions,
tillerDeploymentName string,
tillerNamespace string,
rbacGroups []string,
rbacUsers []string,
Expand Down Expand Up @@ -63,14 +64,14 @@ func GrantAccess(
logger.Infof("Successfully downloaded CA TLS certificates for Tiller deployed in namespace %s.", tillerNamespace)

logger.Infof("Granting access to deployed Tiller in namespace %s to RBAC groups", tillerNamespace)
if err := grantAccessToRBACEntities(kubectlOptions, tlsOptions, caKeyPairPath, tillerNamespace, convertToGroupInfos(rbacGroups)); err != nil {
if err := grantAccessToRBACEntities(kubectlOptions, tlsOptions, caKeyPairPath, tillerDeploymentName, tillerNamespace, convertToGroupInfos(rbacGroups)); err != nil {
logger.Errorf("Error granting access to deployed Tiller in namespace %s to RBAC groups: %s", tillerNamespace, err)
return err
}
logger.Infof("Successfully granted access to deployed Tiller in namespace %s to RBAC groups", tillerNamespace)

logger.Infof("Granting access to deployed Tiller in namespace %s to RBAC users", tillerNamespace)
if err := grantAccessToRBACEntities(kubectlOptions, tlsOptions, caKeyPairPath, tillerNamespace, convertToUserInfos(rbacUsers)); err != nil {
if err := grantAccessToRBACEntities(kubectlOptions, tlsOptions, caKeyPairPath, tillerDeploymentName, tillerNamespace, convertToUserInfos(rbacUsers)); err != nil {
logger.Errorf("Error granting access to deployed Tiller in namespace %s to RBAC users: %s", tillerNamespace, err)
return err
}
Expand All @@ -81,7 +82,7 @@ func GrantAccess(
if err != nil {
return err
}
if err := grantAccessToRBACEntities(kubectlOptions, tlsOptions, caKeyPairPath, tillerNamespace, serviceAccountInfos); err != nil {
if err := grantAccessToRBACEntities(kubectlOptions, tlsOptions, caKeyPairPath, tillerDeploymentName, tillerNamespace, serviceAccountInfos); err != nil {
logger.Errorf("Error granting access to deployed Tiller in namespace %s to Service Accounts: %s", tillerNamespace, err)
return err
}
Expand Down Expand Up @@ -136,6 +137,7 @@ func grantAccessToRBACEntities(
kubectlOptions *kubectl.KubectlOptions,
tlsOptions tls.TLSOptions,
caKeyPairPath tls.CertificateKeyPairPath,
tillerDeploymentName string,
tillerNamespace string,
rbacEntities []RBACEntity,
) error {
Expand All @@ -152,7 +154,7 @@ func grantAccessToRBACEntities(
logger.Infof("Successfully generated and stored certificate key pair for %s", rbacEntity)

logger.Infof("Creating and binding RBAC roles to %s", rbacEntity)
err = createAndBindRBACRolesForTillerAccess(kubectlOptions, tillerNamespace, clientSecretName, rbacEntity)
err = createAndBindRBACRolesForTillerAccess(kubectlOptions, tillerDeploymentName, tillerNamespace, clientSecretName, rbacEntity)
if err != nil {
logger.Errorf("Error creating and binding RBAC roles to %s", rbacEntity)
return err
Expand Down Expand Up @@ -254,6 +256,7 @@ func generateAndStoreSignedCertificateKeyPair(
// - Get the client TLS certificate Secret resource in the tiller namespace.
func createAndBindRBACRolesForTillerAccess(
kubectlOptions *kubectl.KubectlOptions,
tillerDeploymentName string,
tillerNamespace string,
clientSecretName string,
rbacEntity RBACEntity,
Expand All @@ -262,7 +265,7 @@ func createAndBindRBACRolesForTillerAccess(
roleName := getTillerAccessRoleName(rbacEntity.EntityID(), tillerNamespace)

logger.Infof("Creating RBAC role to grant access to Tiller in namespace %s to %s", tillerNamespace, rbacEntity)
err := createTillerRBACRole(kubectlOptions, tillerNamespace, clientSecretName, roleName, rbacEntity)
err := createTillerRBACRole(kubectlOptions, tillerDeploymentName, tillerNamespace, clientSecretName, roleName, rbacEntity)
if err != nil {
logger.Errorf("Error creating RBAC role to grant access to Tiller: %s", err)
return err
Expand All @@ -282,6 +285,7 @@ func createAndBindRBACRolesForTillerAccess(

func createTillerRBACRole(
kubectlOptions *kubectl.KubectlOptions,
tillerDeploymentName string,
tillerNamespace string,
clientSecretName string,
roleName string,
Expand All @@ -294,6 +298,12 @@ func createTillerRBACRole(
APIGroups: []string{""},
Resources: []string{"pods"},
},
rbacv1.PolicyRule{
Verbs: []string{"get"},
APIGroups: []string{"apps"},
Resources: []string{"deployments"},
ResourceNames: []string{tillerDeploymentName},
},
rbacv1.PolicyRule{
Verbs: []string{"get"},
APIGroups: []string{""},
Expand Down
4 changes: 2 additions & 2 deletions helm/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func getHelmHome(t *testing.T) string {
return helmHome
}

func bindNamespaceAdminRole(t *testing.T, ttKubectlOptions *k8s.KubectlOptions, serviceAccountName string) {
func bindNamespaceAdminRole(t *testing.T, ttKubectlOptions *k8s.KubectlOptions, serviceAccountNamespace string, serviceAccountName string) {
clientset, err := k8s.GetKubernetesClientFromOptionsE(t, ttKubectlOptions)
require.NoError(t, err)

Expand All @@ -52,7 +52,7 @@ func bindNamespaceAdminRole(t *testing.T, ttKubectlOptions *k8s.KubectlOptions,
Kind: "ServiceAccount",
APIGroup: "",
Name: serviceAccountName,
Namespace: ttKubectlOptions.Namespace,
Namespace: serviceAccountNamespace,
},
},
RoleRef: rbacv1.RoleRef{
Expand Down

0 comments on commit ec1f2ab

Please sign in to comment.