Skip to content

Commit

Permalink
Enable role binding (#235)
Browse files Browse the repository at this point in the history
* Enable role binding

* Update fiaas_deploy_daemon/config.py

Co-authored-by: Eloy Maillo <[email protected]>

* Update fiaas_deploy_daemon/specs/models.py

Co-authored-by: Eloy Maillo <[email protected]>

* Change role binding deployer implementation

* Ack review comments

* Rename to check_if_matches

* Add tests when role binding exists

* Fix codestyle

* Update fiaas_deploy_daemon/deployer/kubernetes/role_binding.py

Co-authored-by: Øyvind Ingebrigtsen Øvergaard <[email protected]>

* Add test for clean

* Fix style

* Update helm/fiaas-deploy-daemon/templates/custom_rolebinding.yaml

Co-authored-by: Øyvind Ingebrigtsen Øvergaard <[email protected]>

* Improve operator guide and add empty lists in the values example

* Update operator_guide.md

---------

Co-authored-by: Eloy Maillo <[email protected]>
Co-authored-by: herodes1991 <[email protected]>
Co-authored-by: Øyvind Ingebrigtsen Øvergaard <[email protected]>
  • Loading branch information
4 people authored Mar 25, 2024
1 parent 016aaea commit a793765
Show file tree
Hide file tree
Showing 25 changed files with 468 additions and 12 deletions.
11 changes: 11 additions & 0 deletions docs/operator_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,17 @@ specified as `extensions.secrets.foosecrets.annotations` will be added to the po
Using the special value 'default' for the 'type' will mean the image will be attached when an application doesn't
specify any other secrets configuration.

### RoleBinding and ClusterRole Configuration (--list-of-cluster-roles --list-of-roles)

Enables the creation of a `RoleBinding`. If provided a list-of-cluster-roles, it will generate the `RoleBinding` with the kind `ClusterRole`. If provided a list-of-roles, it will generate the `RoleBinding` with the kind `Role`.
This is useful for when you wish all applications to have read access to specific resources. Also you will need to include the roles and rolebindings in the values.yaml file, under the keys `rbac.roleBinding.roles` and `rbac.roleBinding.clusterRoles`

##### List of ClusterRoles and Roles for Role Binding

- `--list-of-cluster-roles=["A", "B"]`: This flag allows the creation of `RoleBinding` objects with `ClusterRole` kind for the specified cluster-level roles.

- `--list-of-roles=["A", "B"]`: This flag allows the creation of `RoleBinding` objects with `Role` kind for the specified namespace-level roles.

#### Strongbox

In AWS you have the option of using [Strongbox](https://https://github.com/schibsted/strongbox) for your secrets. If you wish to use Strongbox, the configuration option `strongbox-init-container-image` should be set to an image that can get secrets from Strongbox. This option is very similar to the previous variant, except that Strongbox gets a few more pieces of information from the application configuration. The application must specify an IAM role, and can select AWS region and a list of secret groups to get secrets from.
Expand Down
2 changes: 2 additions & 0 deletions docs/v3_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ annotations:
service_account: {}
pod: {}
pod_disruption_budget: {}
role_binding: {}
```

The annotations are organized under the Kubernetes resource they will be applied to. To specify custom anotations, set
Expand Down Expand Up @@ -637,6 +638,7 @@ labels:
service_account: {}
pod: {}
pod_disruption_budget: {}
role_binding: {}
```

The labels are organized under the Kubernetes resource they will be applied to. To specify custom labels, set
Expand Down
16 changes: 16 additions & 0 deletions fiaas_deploy_daemon/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,22 @@ def _parse_args(self, args):
"--include-status-in-app", help="Include status subresource in application CRD", default=False,
action="store_true"
)
parser.add_argument(
"--list-of-roles",
help="list of roles",
default=[],
action="append",
type=str,
dest="list_of_roles",
)
parser.add_argument(
"--list-of-cluster-roles",
help="list of clusterroles",
default=[],
action="append",
type=str,
dest="list_of_cluster_roles",
)
parser.add_argument(
"--pdb-max-unavailable",
help="The maximum number of pods that can be unavailable after an eviction",
Expand Down
1 change: 1 addition & 0 deletions fiaas_deploy_daemon/crd/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class AdditionalLabelsOrAnnotations(Model):
pod = Field(dict)
status = Field(dict)
pod_disruption_budget = Field(dict)
role_binding = Field(dict)


class FiaasApplicationSpec(Model):
Expand Down
2 changes: 2 additions & 0 deletions fiaas_deploy_daemon/deployer/kubernetes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .service import ServiceDeployer
from .service_account import ServiceAccountDeployer
from .owner_references import OwnerReferences
from .role_binding import RoleBindingDeployer
from .pod_disruption_budget import PodDisruptionBudgetDeployer
from k8s.models.ingress import IngressTLS as V1Beta1IngressTLS
from k8s.models.networking_v1_ingress import IngressTLS as StableIngressTLS
Expand All @@ -45,6 +46,7 @@ def configure(self, bind):
bind("ingress_tls_deployer", to_class=IngressTLSDeployer)
bind("owner_references", to_class=OwnerReferences)
bind("pod_disruption_budget_deployer", to_class=PodDisruptionBudgetDeployer)
bind("role_binding_deployer", to_class=RoleBindingDeployer)

if self.use_networkingv1_ingress:
bind("ingress_adapter", to_class=NetworkingV1IngressAdapter)
Expand Down
6 changes: 5 additions & 1 deletion fiaas_deploy_daemon/deployer/kubernetes/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .service import ServiceDeployer
from .service_account import ServiceAccountDeployer
from .pod_disruption_budget import PodDisruptionBudgetDeployer
from .role_binding import RoleBindingDeployer

LOG = logging.getLogger(__name__)

Expand All @@ -37,7 +38,8 @@ class K8s(object):

def __init__(
self, config, service_deployer, deployment_deployer, ingress_deployer,
autoscaler, service_account_deployer, pod_disruption_budget_deployer
autoscaler, service_account_deployer, pod_disruption_budget_deployer,
role_binding_deployer
):
self._version = config.version
self._enable_service_account_per_app = config.enable_service_account_per_app
Expand All @@ -47,6 +49,7 @@ def __init__(
self._autoscaler_deployer: AutoscalerDeployer = autoscaler
self._service_account_deployer: ServiceAccountDeployer = service_account_deployer
self._pod_disruption_budget_deployer: PodDisruptionBudgetDeployer = pod_disruption_budget_deployer
self._role_binding_deployer: RoleBindingDeployer = role_binding_deployer

def deploy(self, app_spec: AppSpec):
if _besteffort_qos_is_required(app_spec):
Expand All @@ -55,6 +58,7 @@ def deploy(self, app_spec: AppSpec):
labels = self._make_labels(app_spec)
if self._enable_service_account_per_app is True:
self._service_account_deployer.deploy(app_spec, labels)
self._role_binding_deployer.deploy(app_spec, labels)
self._service_deployer.deploy(app_spec, selector, labels)
self._ingress_deployer.deploy(app_spec, labels)
self._deployment_deployer.deploy(app_spec, selector, labels, _besteffort_qos_is_required(app_spec))
Expand Down
100 changes: 100 additions & 0 deletions fiaas_deploy_daemon/deployer/kubernetes/role_binding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import logging

from k8s.models.common import ObjectMeta
from k8s.models.role_binding import RoleBinding, RoleRef, Subject
from fiaas_deploy_daemon.specs.models import AppSpec
from fiaas_deploy_daemon.deployer.kubernetes.owner_references import OwnerReferences
from fiaas_deploy_daemon.tools import merge_dicts

LOG = logging.getLogger(__name__)


class RoleBindingDeployer:
def __init__(self, config, owner_references):
self._owner_references: OwnerReferences = owner_references
self._list_of_roles = config.list_of_roles
self._list_of_cluster_roles = config.list_of_cluster_roles

def deploy(self, app_spec: AppSpec, labels):
custom_annotations = {}
custom_labels = labels
custom_labels = merge_dicts(app_spec.labels.role_binding, custom_labels)
custom_annotations = merge_dicts(app_spec.annotations.role_binding, custom_annotations)
# Getting list of rolebindings with the label app=app_name
role_bindings = RoleBinding.find(name=app_spec.name, namespace=app_spec.namespace)
self._clean_not_needed_role_bindings(role_bindings)
self._update_or_create_role_bindings(app_spec, self._list_of_roles, "Role", custom_annotations, custom_labels, role_bindings)
self._update_or_create_role_bindings(app_spec, self._list_of_cluster_roles, "ClusterRole", custom_annotations, custom_labels,
role_bindings)

def _update_or_create_role_bindings(self, app_spec: AppSpec, roles_list, role_kind, custom_annotations, custom_labels,
role_bindings):
namespace = app_spec.namespace
service_account_name = app_spec.name
for role_name in roles_list:
role_binding = self._find_role_in_role_bindings(role_kind, role_name, role_bindings)
if role_binding:
if self._owned_by_fiaas(role_binding):
LOG.info("Updating RoleBinding %s", role_binding.metadata.name)
generate = False
role_binding_name = role_binding.metadata.name
else:
LOG.info(
"Aborting the creation of a roleBinding for Application: %s with %s: %s, role is already bound by %s",
app_spec.name,
role_kind,
role_name,
role_binding.metadata.name
)
continue
else:
role_binding = RoleBinding()
role_binding_name = f"{app_spec.name}-"
LOG.info("Creating a new rolebinding for %s, the name will be generated by K8s with the prefix %s",
app_spec.name, role_binding_name)
generate = True

self._deploy_role_binding(app_spec, role_kind, custom_annotations, custom_labels, namespace,
service_account_name, role_name, role_binding, role_binding_name, generate)

def _deploy_role_binding(self, app_spec, role_kind, custom_annotations, custom_labels, namespace,
service_account_name, role_name, role_binding, role_binding_name, generate):
if generate:
role_binding.metadata = ObjectMeta(generateName=role_binding_name, namespace=namespace,
labels=custom_labels, annotations=custom_annotations)
else:
role_binding.metadata = ObjectMeta(name=role_binding_name, namespace=namespace, labels=custom_labels,
annotations=custom_annotations)

role_ref = RoleRef(kind=role_kind, apiGroup="rbac.authorization.k8s.io", name=role_name)
subject = Subject(kind="ServiceAccount", name=service_account_name, namespace=namespace)
role_binding.roleRef = role_ref
role_binding.subjects = [subject]
self._owner_references.apply(role_binding, app_spec)
role_binding.save()

def _find_role_in_role_bindings(self, role_kind, role_name, role_bindings: list[RoleBinding]):
for role_binding in role_bindings:
if role_binding.roleRef.kind == role_kind and role_binding.roleRef.name == role_name:
return role_binding
return None

def _clean_not_needed_role_bindings(self, role_bindings: list[RoleBinding]):
role_bindings_aux = role_bindings.copy()
for role_binding in role_bindings_aux:
if not self._should_bind(role_binding):
if self._owned_by_fiaas(role_binding):
LOG.info("Deleting RoleBinding %s", role_binding.metadata.name)
RoleBinding.delete(role_binding.metadata.name, role_binding.metadata.namespace)
role_bindings.remove(role_binding)

def _owned_by_fiaas(self, role_binding):
return any(
ref.apiVersion == "fiaas.schibsted.io/v1" and ref.kind == "Application"
for ref in role_binding.metadata.ownerReferences
)

# Check if matches the role_binding with any role or clusterRole in the list_of_roles or list_of_cluster_roles
def _should_bind(self, role_binding):
return (role_binding.roleRef.kind == "Role" and role_binding.roleRef.name in self._list_of_roles) \
or (role_binding.roleRef.kind == "ClusterRole" and role_binding.roleRef.name in self._list_of_cluster_roles)
1 change: 1 addition & 0 deletions fiaas_deploy_daemon/specs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def version(self):
"pod",
"status",
"pod_disruption_budget",
"role_binding",
],
)

Expand Down
2 changes: 2 additions & 0 deletions fiaas_deploy_daemon/specs/v3/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ annotations: # Annotations to be set on generated objects
service: {}
pod: {}
pod_disruption_budget: {}
role_binding: {}
labels: # Labels to be set on generated objects
deployment: {}
horizontal_pod_autoscaler: {}
Expand All @@ -93,6 +94,7 @@ labels: # Labels to be set on generated objects
service: {}
pod: {}
pod_disruption_budget: {}
role_binding: {}
secrets_in_environment: false # If a Secret of the same name exists, it is exposed to the pod as environment variables
admin_access: false # What access the pod has to the k8s api server
extensions:
Expand Down
1 change: 1 addition & 0 deletions fiaas_deploy_daemon/specs/v3/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ def _labels_annotations_spec(labels_annotations_lookup, overrides):
"pod": dict(labels_annotations_lookup["pod"]),
"status": {},
"pod_disruption_budget": dict(labels_annotations_lookup["pod_disruption_budget"]),
"role_binding": dict(labels_annotations_lookup["role_binding"]),
}
if overrides:
globals = _get_value("_global", overrides)
Expand Down
95 changes: 95 additions & 0 deletions helm/fiaas-deploy-daemon/templates/custom_rolebinding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{{- if .Values.rbac.roleBinding.roles -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Values.name }}-role-grantor
labels:
{{ include "fiaas-deploy-daemon.labels" . | indent 4 }}
{{ include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.rbac.roleBinding.labels | indent 4 }}
{{- if or .Values.annotations.global .Values.rbac.roleBinding.annotations }}
annotations:
{{- include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.annotations.global | indent 4 }}
{{- include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.rbac.roleBinding.annotations | indent 4 }}
{{- end }}
rules:
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- bind
resourceNames:
{{- range $role := .Values.rbac.roleBinding.roles }}
- {{ $role }}
{{- end}}

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Values.name }}-rb-role-grantor
labels:
{{ include "fiaas-deploy-daemon.labels" . | indent 4 }}
{{ include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.rbac.roleBinding.labels | indent 4 }}
{{- if or .Values.annotations.global .Values.rbac.roleBinding.annotations }}
annotations:
{{- include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.annotations.global | indent 4 }}
{{- include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.rbac.roleBinding.annotations | indent 4 }}
{{- end }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Values.name }}-role-grantor
subjects:
- kind: ServiceAccount
name: {{ .Values.name }}
namespace: {{ .Release.Namespace }}
{{- end }}

{{- if .Values.rbac.roleBinding.clusterRoles }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Values.name }}-clusterrole-grantor
labels:
{{ include "fiaas-deploy-daemon.labels" . | indent 4 }}
{{ include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.rbac.roleBinding.labels | indent 4 }}
{{- if or .Values.annotations.global .Values.rbac.roleBinding.annotations }}
annotations:
{{- include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.annotations.global | indent 4 }}
{{- include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.rbac.roleBinding.annotations | indent 4 }}
{{- end }}
rules:
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterroles
verbs:
- bind
resourceNames:
{{- range $clusterRole := .Values.rbac.roleBinding.clusterRoles }}
- {{ $clusterRole }}
{{- end}}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Values.name }}-rb-clusterrole-grantor
labels:
{{ include "fiaas-deploy-daemon.labels" . | indent 4 }}
{{ include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.rbac.roleBinding.labels | indent 4 }}
{{- if or .Values.annotations.global .Values.rbac.roleBinding.annotations }}
annotations:
{{- include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.annotations.global | indent 4 }}
{{- include "fiaas-deploy-daemon.labelsOrAnnotations" .Values.rbac.roleBinding.annotations | indent 4 }}
{{- end }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Values.name }}-clusterrole-grantor
subjects:
- kind: ServiceAccount
name: {{ .Values.name }}
namespace: {{ .Release.Namespace }}
{{- end }}
2 changes: 2 additions & 0 deletions helm/fiaas-deploy-daemon/templates/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ rules:
- extensions
- networking.k8s.io
- policy
- rbac.authorization.k8s.io
resources:
- configmaps
- deployments
Expand All @@ -59,6 +60,7 @@ rules:
- resourcequotas
- services
- serviceaccounts
- rolebindings
verbs:
- create
- delete
Expand Down
2 changes: 2 additions & 0 deletions helm/fiaas-deploy-daemon/values.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ rbac:
create: true
labels: {}
annotations: {}
roles: []
clusterRoles: []
clusterRole:
create: true
labels: {}
Expand Down
4 changes: 2 additions & 2 deletions tests/fiaas_deploy_daemon/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ def app_spec():
teams=["foo"],
tags=["bar"],
deployment_id="test_app_deployment_id",
labels=LabelAndAnnotationSpec({}, {}, {}, {}, {}, {}, {}, {}),
annotations=LabelAndAnnotationSpec({}, {}, {}, {}, {}, {}, {}, {}),
labels=LabelAndAnnotationSpec({}, {}, {}, {}, {}, {}, {}, {}, {}),
annotations=LabelAndAnnotationSpec({}, {}, {}, {}, {}, {}, {}, {}, {}),
ingresses=[
IngressItemSpec(host=None, pathmappings=[IngressPathMappingSpec(path="/", port=80)], annotations={})
],
Expand Down
Loading

0 comments on commit a793765

Please sign in to comment.