Skip to content

Commit

Permalink
Merge pull request #258 from mkubaczyk/custom-tiller-role
Browse files Browse the repository at this point in the history
Add TillerRoleTemplateFile to allow create Tiller with custom role
  • Loading branch information
Sami Alajrami authored Jun 24, 2019
2 parents d896bed + b11c465 commit 1f61640
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 54 deletions.
12 changes: 10 additions & 2 deletions docs/desired_state_specification.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
version: v1.9.1
version: v1.10.0
---

# Helmsman desired state specification
Expand Down Expand Up @@ -146,6 +146,12 @@ Options:
- **installTiller**: defines if Tiller should be deployed in this namespace or not. Default is false. Any chart desired to be deployed into a namespace with a Tiller deployed, will be deployed using that Tiller and not the one in kube-system unless you use the `TillerNamespace` option (see the [Apps](#apps) section below) to use another Tiller.
> By default Tiller will be deployed into `kube-system` even if you don't define kube-system in the namespaces section. To prevent deploying Tiller into `kube-system, add kube-system in your namespaces section and set its installTiller to false.
- **tillerRole**: specify the role to use. If 'cluster-admin' a clusterrolebinding will be used else a role with a single namespace scope will be created and bound with a rolebinding.
- **tillerRoleTemplateFile**: relative path to file templating custom Tiller role. If `installTiller` is true and `tillerRole` is not `cluster-admin`, then helmsman will create namespace specific Tiller role based on the template file passed with this parameter. When `tillerRole` is empty string, role name defaults to `helmsman-tiller`.

If `tillerRoleTemplateFile` is set, it will always try to create namespace-scoped Tiller with conditions like below:
- if `tillerRole` was not set, default name of `helmsman-tiller` will be used to createa new Role
- if `tillerServiceAccount` was not set, it will create Service Account with default name of `helmsman`

- **useTiller**: defines that you would like to use an existing Tiller from that namespace. Can't be set together with `installTiller`
- **labels** : defines labels to be added to the namespace, doesn't remove existing labels but updates them if the label key exists with any other different value. You can define any key/value pairs. Default is empty.
- **annotations** : defines annotations to be added to the namespace. It behaves the same way as the labels option.
Expand All @@ -154,7 +160,7 @@ Options:
- **tillerServiceAccount**: defines what service account to use when deploying Tiller. If this is not set, the following options are considered:

1. If the `serviceAccount` defined in the `settings` section exists in the namespace you want to deploy Tiller in, it will be used.
2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml).
2. If you defined `serviceAccount` in the `settings` section and it does not exist in the namespace you want to deploy Tiller in, Helmsman creates the service account in that namespace and binds it to a (cluster)role. If the namespace is kube-system and `tillerRole` is unset or is set to cluster-admin, the service account is bound to `cluster-admin` clusterrole. Otherwise, if you specified a `tillerRole`, a new role with that name is created and bound to the service account with rolebinding. If `tillerRole` is unset (for namespaces other than kube-system), the role is called `helmsman-tiller` and is created in the specified namespace to only gives access to that namespace. The custom role is created from a [yaml template](../data/role.yaml) if no `tillerRoleTemplateFile` was set, or it will use the `tillerRoleTemplateFile` (of the same structure as [yaml template](../data/role.yaml)) to create Role from.

> If `installTiller` is not defined or set to false, this flag is ignored.
Expand All @@ -180,6 +186,7 @@ protected = false
protected = true
installTiller = true
tillerServiceAccount = "tiller-production"
tillerRoleTemplateFile = "../roles/helmsman-tiller.yaml"
caCert = "secrets/ca.cert.pem"
tillerCert = "secrets/tiller.cert.pem"
tillerKey = "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem
Expand Down Expand Up @@ -216,6 +223,7 @@ namespaces:
protected: true
installTiller: true
tillerServiceAccount: "tiller-production"
tillerRoleTemplateFile: "../roles/helmsman-tiller.yaml"
caCert: "secrets/ca.cert.pem"
tillerCert: "secrets/tiller.cert.pem"
tillerKey: "$TILLER_KEY" # where TILLER_KEY=secrets/tiller.key.pem
Expand Down
1 change: 1 addition & 0 deletions docs/how_to/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This page contains a list of guides on how to use Helmsman.
- [Prevent Deploying Tiller in kube-system](tiller/prevent_tiller_in_kube_system.md)
- [Deploy Multiple Tillers with custom setup for each](tiller/multitenancy.md)
- [Deploy apps with specific Tillers](tiller/deploy_apps_with_specific_tiller.md)
- [Custom Tiller Role](tiller/custom_tiller_role.md)
- Defining Helm repositories
- [Using default helm repos](helm_repos/default.md)
- [Using private repos in Google GCS](helm_repos/gcs.md)
Expand Down
37 changes: 37 additions & 0 deletions docs/how_to/tiller/custom_tiller_role.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
version: v1.10.0
---

# Create helmsman per namespace with your custom Role

You can deploy namespace-specific helmsman Tiller with Service Account having custom Role.

By default, when defining Namespaces in Desired State Specification file, when `installTiller` is enabled for specific namespace,
it creates the Role to bind the Tiller to with default [yaml template](../../../data/role.yaml).

If there's a need for custom Role (let's say each namespace has its different and specific requirements to permissions),
you can define `tillerRoleTemplateFile`, which is a relative path pointing at a template of a Role (same format as a [yaml template](../../../data/role.yaml)),
so when Helmsman creates Tiller in the namespace with this key, custom Role will be created for Tiller.

```toml
[namespaces]
[namespaces.dev]
useTiller = true
[namespaces.production]
installTiller = true
tillerServiceAccount = "tiller-production"
tillerRoleTemplateFile = "../roles/helmsman-tiller.yaml"
```

```yaml
namespaces:
dev:
useTiller: true
production:
installTiller: true
tillerServiceAccount: "tiller-production"
tillerRoleTemplateFile: "../roles/helmsman-tiller.yaml"
```
The example above will create two namespaces: dev and production, where dev namespace will have its Tiller with default Role,
while production namespace will be managed by its specific Tiller having custom role based on the `"../roles/helmsman-tiller.yaml"` template created by you.
33 changes: 21 additions & 12 deletions helm_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,37 +346,46 @@ func addHelmRepos(repos map[string]string) (bool, string) {
// If serviceAccount is not provided (empty string), the defaultServiceAccount is used.
// If no defaultServiceAccount is provided, A service account is created and Tiller is deployed with the new service account
// If no namespace is provided, Tiller is deployed to kube-system
func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string) (bool, string) {
func deployTiller(namespace string, serviceAccount string, defaultServiceAccount string, role string, roleTemplateFile string) (bool, string) {
log.Println("INFO: deploying Tiller in namespace [ " + namespace + " ].")
sa := ""
if serviceAccount != "" {
if ok, err := validateServiceAccount(serviceAccount, namespace); !ok {
if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") {

log.Println("INFO: service account [ " + serviceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ")
if _, rbacErr := createRBAC(serviceAccount, namespace, role); rbacErr != "" {
if _, rbacErr := createRBAC(serviceAccount, namespace, role, roleTemplateFile); rbacErr != "" {
return false, rbacErr
}
} else {
return false, "ERROR: while validating/creating service account [ " + serviceAccount + " ] in namespace [" + namespace + "]: " + err
}
}
sa = "--service-account " + serviceAccount
} else if defaultServiceAccount != "" {
if ok, err := validateServiceAccount(defaultServiceAccount, namespace); !ok {
} else {
roleName := "helmsman-tiller"
defaultServiceAccountName := "helmsman"

if defaultServiceAccount != "" {
defaultServiceAccountName = defaultServiceAccount
}
if role != "" {
roleName = role
}

if ok, err := validateServiceAccount(defaultServiceAccountName, namespace); !ok {
if strings.Contains(err, "NotFound") || strings.Contains(err, "not found") {

log.Println("INFO: service account [ " + defaultServiceAccount + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ")
if _, rbacErr := createRBAC(defaultServiceAccount, namespace, role); rbacErr != "" {
log.Println("INFO: service account [ " + defaultServiceAccountName + " ] does not exist in namespace [ " + namespace + " ] .. attempting to create it ... ")
if _, rbacErr := createRBAC(defaultServiceAccountName, namespace, roleName, roleTemplateFile); rbacErr != "" {
return false, rbacErr
}
} else {
return false, "ERROR: while validating/creating service account [ " + defaultServiceAccount + " ] in namespace [ " + namespace + "]: " + err
return false, "ERROR: while validating/creating service account [ " + defaultServiceAccountName + " ] in namespace [" + namespace + "]: " + err
}
}
sa = "--service-account " + defaultServiceAccount
sa = "--service-account " + defaultServiceAccountName
}

if namespace == "" {
namespace = "kube-system"
}
Expand Down Expand Up @@ -438,20 +447,20 @@ func initHelm() (bool, string) {
downloadFile(s.Namespaces[k].ClientKey, k+"-client.key")
}
if ns.InstallTiller && k != "kube-system" {
if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok {
if ok, err := deployTiller(k, ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile); !ok {
return false, err
}
}
}

if ns, ok := s.Namespaces["kube-system"]; ok {
if ns.InstallTiller {
if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole); !ok {
if ok, err := deployTiller("kube-system", ns.TillerServiceAccount, defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile); !ok {
return false, err
}
}
} else {
if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole); !ok {
if ok, err := deployTiller("kube-system", "", defaultSA, ns.TillerRole, ns.TillerRoleTemplateFile); !ok {
return false, err
}
}
Expand Down
1 change: 0 additions & 1 deletion init.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ func init() {
} else {
logError(msg)
}

// Merge Apps that already existed in the state
for appName, app := range fileState.Apps {
if _, ok := s.Apps[appName]; ok {
Expand Down
17 changes: 12 additions & 5 deletions kube_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func validateServiceAccount(sa string, namespace string) (bool, string) {
// createRBAC creates a k8s service account and bind it to a (Cluster)Role
// role can be "cluster-admin" or any other custom name
// It binds it to a new role called "helmsman-tiller"
func createRBAC(sa string, namespace string, role string) (bool, string) {
func createRBAC(sa string, namespace string, role string, roleTemplateFile string) (bool, string) {
var ok bool
var err string
if role == "" {
Expand All @@ -48,7 +48,7 @@ func createRBAC(sa string, namespace string, role string) (bool, string) {
}
return false, err
}
if ok, err = createRole(namespace, role); ok {
if ok, err = createRole(namespace, role, roleTemplateFile); ok {
if ok, err = createRoleBinding(role, sa, namespace); ok {
return true, ""
}
Expand Down Expand Up @@ -377,10 +377,17 @@ func createRoleBinding(role string, saName string, namespace string) (bool, stri
}

// createRole creates a k8s Role in a given namespace from a template
func createRole(namespace string, role string) (bool, string) {
func createRole(namespace string, role string, roleTemplateFile string) (bool, string) {
var resource []byte
var e error

// load static resource
resource, e := Asset("data/role.yaml")
if roleTemplateFile != "" {
// load file from path of TillerRoleTemplateFile
resource, e = ioutil.ReadFile(roleTemplateFile)
} else {
// load static resource
resource, e = Asset("data/role.yaml")
}
if e != nil {
logError(e.Error())
}
Expand Down
27 changes: 14 additions & 13 deletions namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,20 @@ type limits []struct {

// namespace type represents the fields of a namespace
type namespace struct {
Protected bool `yaml:"protected"`
InstallTiller bool `yaml:"installTiller"`
UseTiller bool `yaml:"useTiller"`
TillerServiceAccount string `yaml:"tillerServiceAccount"`
TillerRole string `yaml:"tillerRole"`
CaCert string `yaml:"caCert"`
TillerCert string `yaml:"tillerCert"`
TillerKey string `yaml:"tillerKey"`
ClientCert string `yaml:"clientCert"`
ClientKey string `yaml:"clientKey"`
Limits limits `yaml:"limits,omitempty"`
Labels map[string]string `yaml:"labels"`
Annotations map[string]string `yaml:"annotations"`
Protected bool `yaml:"protected"`
InstallTiller bool `yaml:"installTiller"`
UseTiller bool `yaml:"useTiller"`
TillerServiceAccount string `yaml:"tillerServiceAccount"`
TillerRole string `yaml:"tillerRole"`
TillerRoleTemplateFile string `yaml:"tillerRoleTemplateFile"`
CaCert string `yaml:"caCert"`
TillerCert string `yaml:"tillerCert"`
TillerKey string `yaml:"tillerKey"`
ClientCert string `yaml:"clientCert"`
ClientKey string `yaml:"clientKey"`
Limits limits `yaml:"limits,omitempty"`
Labels map[string]string `yaml:"labels"`
Annotations map[string]string `yaml:"annotations"`
}

// checkNamespaceDefined checks if a given namespace is defined in the namespaces section of the desired state file
Expand Down
2 changes: 1 addition & 1 deletion release_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func Test_validateRelease(t *testing.T) {
Metadata: make(map[string]string),
Certificates: make(map[string]string),
Settings: (config{}),
Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}},
Namespaces: map[string]namespace{"namespace": namespace{false, false, false, "", "", "", "", "", "", "", "", (limits{}), make(map[string]string), make(map[string]string)}},
HelmRepos: make(map[string]string),
Apps: make(map[string]*release),
}
Expand Down
Loading

0 comments on commit 1f61640

Please sign in to comment.