Skip to content

Instantly share code, notes, and snippets.

@abursavich
Last active June 1, 2021 04:38
Show Gist options
  • Save abursavich/03a48e23d4c1c5b03f6406b8922324a7 to your computer and use it in GitHub Desktop.
Save abursavich/03a48e23d4c1c5b03f6406b8922324a7 to your computer and use it in GitHub Desktop.
kubespec usage example
import hashlib
from typing import Dict, List
import yaml
from kubespec import context, types
from kubespec.k8s import base
from kubespec.k8s.api.apps import v1 as appsv1
from kubespec.k8s.api.core import v1 as corev1
from kubespec.k8s.api.rbac import v1 as rbacv1
from kubespec.k8s.apiextensions.apiextensions import v1beta1 as apiextv1b1
from kubespec.k8s.apimachinery.meta import v1 as metav1
from typeguard import typechecked
NAME = "sealed-secrets"
NAMESPACE = "sealed-secrets"
OWNER = "techops-sre"
# namespace-level names
CONTROLLER_NAME = "controller"
SERVICE_PROXIER_NAME = "controller-service-proxier"
KEY_ADMIN_NAME = "key-admin"
# cluster-level names
CLUSTER_CONTROLLER_NAME = "sealed-secrets-controller"
CLUSTER_UNSEALER_NAME = "sealed-secrets-unsealer"
class _VersionMixin(types.Object):
@context.scoped
@typechecked
def __init__(self, version: str = "unknown", **kwargs):
super().__init__(**kwargs)
self._version = version
def version(self) -> str:
return self._version
class _LabelsMixin(_VersionMixin, base.MetadataObject):
def selectorLabels(self) -> Dict[str, str]:
md5 = hashlib.md5()
md5.update(NAME.encode("utf8"))
md5.update(NAMESPACE.encode("utf8"))
return {
"app.kubernetes.io/name": self.name(),
"app.kubernetes.io/part-of": NAME,
"app.kubernetes.io/instance": NAME + "-" + md5.hexdigest()[-6:],
}
def labels(self) -> Dict[str, str]:
labels = super().labels().copy()
labels.update(self.selectorLabels())
labels["app.kubernetes.io/managed-by"] = "kubespec"
labels["app.kubernetes.io/version"] = self.version()
labels["mz.com/owner"] = OWNER
return labels
class _NamespaceMixin:
def namespace(self) -> str:
return NAMESPACE
class _BaseMixin(_NamespaceMixin, _LabelsMixin):
pass
class _ControllerMixin(_BaseMixin):
def name(self) -> str:
return CONTROLLER_NAME
class _ControllerRoleBindingMixin(_ControllerMixin):
def subjects(self) -> List[rbacv1.Subject]:
return [
rbacv1.Subject(
kind="ServiceAccount", name=CONTROLLER_NAME, namespace=NAMESPACE
)
]
class Namespace(_LabelsMixin, corev1.Namespace):
def name(self) -> str:
return NAMESPACE
class CustomResourceDefinition(_BaseMixin, apiextv1b1.CustomResourceDefinition):
def name(self) -> str:
return "sealedsecrets.bitnami.com"
def spec(self) -> apiextv1b1.CustomResourceDefinitionSpec:
return apiextv1b1.CustomResourceDefinitionSpec(
group="bitnami.com",
names=apiextv1b1.CustomResourceDefinitionNames(
kind="SealedSecret",
listKind="SealedSecretList",
plural="sealedsecrets",
singular="sealedsecret",
),
scope=apiextv1b1.ResourceScope.NamespaceScoped,
versions=[
apiextv1b1.CustomResourceDefinitionVersion(
name="v1alpha1", served=True, storage=True
)
],
)
class ServiceAccount(_ControllerMixin, corev1.ServiceAccount):
pass
class KeyAdminRole(_BaseMixin, rbacv1.Role):
def name(self) -> str:
return KEY_ADMIN_NAME
def rules(self) -> List[rbacv1.PolicyRule]:
return [
rbacv1.PolicyRule(
apiGroups=[""], resources=["secrets"], verbs=["create", "list"]
)
]
class RoleBinding(_ControllerRoleBindingMixin, rbacv1.RoleBinding):
def roleRef(self) -> rbacv1.RoleRef:
return rbacv1.RoleRef(kind="Role", name=KEY_ADMIN_NAME)
class UnsealerClusterRole(_BaseMixin, rbacv1.ClusterRole):
def name(self) -> str:
return CLUSTER_UNSEALER_NAME
def rules(self) -> List[rbacv1.PolicyRule]:
return [
rbacv1.PolicyRule(
apiGroups=["bitnami.com"],
resources=["sealedsecrets"],
verbs=["get", "list", "watch", "update"],
),
rbacv1.PolicyRule(
apiGroups=[""],
resources=["secrets"],
verbs=["get", "create", "update", "delete"],
),
rbacv1.PolicyRule(
apiGroups=[""], resources=["events"], verbs=["create", "patch"]
),
]
class ClusterRoleBinding(_ControllerRoleBindingMixin, rbacv1.ClusterRoleBinding):
def name(self) -> str:
return CLUSTER_CONTROLLER_NAME
def roleRef(self) -> rbacv1.RoleRef:
return rbacv1.RoleRef(kind="ClusterRole", name=CLUSTER_UNSEALER_NAME)
class Deployment(_ControllerMixin, appsv1.Deployment):
def spec(self) -> appsv1.DeploymentSpec:
port = 8080
volume = corev1.Volume(
name="tmp",
volumeSource=corev1.VolumeSource(emptyDir=corev1.EmptyDirVolumeSource()),
)
probe = corev1.Probe(
handler=corev1.Handler(
httpGet=corev1.HTTPGetAction(path="/healthz", port="http")
)
)
container = corev1.Container(
name="controller",
image="quay.io/bitnami/sealed-secrets-controller:v" + self.version(),
args=["--listen-addr", ":" + str(port)],
ports=[corev1.ContainerPort(name="http", containerPort=port)],
livenessProbe=probe,
readinessProbe=probe,
securityContext=corev1.SecurityContext(
readOnlyRootFilesystem=True, runAsNonRoot=True, runAsUser=1001
),
volumeMounts=[corev1.VolumeMount(name="tmp", mountPath="/tmp")],
)
return appsv1.DeploymentSpec(
selector=metav1.LabelSelector(matchLabels=self.selectorLabels()),
template=corev1.PodTemplateSpec(
labels=self.labels(),
spec=corev1.PodSpec(
containers=[container],
volumes=[volume],
serviceAccountName=CONTROLLER_NAME,
),
),
)
class Service(_ControllerMixin, corev1.Service):
def spec(self) -> corev1.ServiceSpec:
return corev1.ServiceSpec(
selector=self.selectorLabels(),
ports=[corev1.ServicePort(name="http", targetPort="http", port=80)],
)
class ServiceProxierRole(_BaseMixin, rbacv1.Role):
def name(self) -> str:
return SERVICE_PROXIER_NAME
def rules(self) -> List[rbacv1.PolicyRule]:
return [
rbacv1.PolicyRule(
apiGroups=[""],
resources=["services/proxy"],
resourceNames=[
# kubeseal uses net.JoinSchemeNamePort when crafting proxy subresource URLs
"http:" + CONTROLLER_NAME + ":",
# but often services are referred by name only, let's not make it unnecessarily cryptic
CONTROLLER_NAME,
],
verbs=[
# rotate and validate endpoints expect POST,
# see https://kubernetes.io/docs/reference/access-authn-authz/authorization/#determine-the-request-verb
"create",
"get",
],
)
]
class ServiceProxierRoleBinding(_BaseMixin, rbacv1.RoleBinding):
def name(self) -> str:
return SERVICE_PROXIER_NAME
def subjects(self) -> List[rbacv1.Subject]:
return [
rbacv1.Subject(
apiGroup="rbac.authorization.k8s.io",
kind="Group",
name="system:authenticated",
)
]
def roleRef(self) -> rbacv1.RoleRef:
return rbacv1.RoleRef(kind="Role", name=SERVICE_PROXIER_NAME)
def _print_yaml(*objs) -> str:
for i, obj in enumerate(objs):
if i > 0:
print("---")
print(yaml.dump(obj.render()))
if __name__ == "__main__":
with context.Scope(version="0.9.2"):
_print_yaml(
Namespace(),
CustomResourceDefinition(),
ServiceAccount(),
KeyAdminRole(),
RoleBinding(),
UnsealerClusterRole(),
ClusterRoleBinding(),
Deployment(),
Service(),
ServiceProxierRole(),
ServiceProxierRoleBinding(),
)
apiVersion: v1
kind: Namespace
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: sealed-secrets
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: sealed-secrets
spec: {}
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: sealedsecrets.bitnami.com
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: sealedsecrets.bitnami.com
spec:
conversion:
strategy: None
group: bitnami.com
names:
kind: SealedSecret
listKind: SealedSecretList
plural: sealedsecrets
singular: sealedsecret
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: controller
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: controller
namespace: sealed-secrets
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: key-admin
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: key-admin
namespace: sealed-secrets
rules:
- apiGroups:
- ''
resources:
- secrets
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: controller
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: controller
namespace: sealed-secrets
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: key-admin
subjects:
- kind: ServiceAccount
name: controller
namespace: sealed-secrets
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: sealed-secrets-unsealer
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: sealed-secrets-unsealer
rules:
- apiGroups:
- bitnami.com
resources:
- sealedsecrets
verbs:
- get
- list
- watch
- update
- apiGroups:
- ''
resources:
- secrets
verbs:
- get
- create
- update
- delete
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: sealed-secrets-controller
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: sealed-secrets-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sealed-secrets-unsealer
subjects:
- kind: ServiceAccount
name: controller
namespace: sealed-secrets
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: controller
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: controller
namespace: sealed-secrets
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/name: controller
app.kubernetes.io/part-of: sealed-secrets
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: controller
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
spec:
containers:
- args:
- --listen-addr
- :8080
image: quay.io/bitnami/sealed-secrets-controller:v0.9.2
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: http
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
name: controller
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: http
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources: {}
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1001
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /tmp
name: tmp
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
serviceAccountName: controller
terminationGracePeriodSeconds: 30
volumes:
- emptyDir: {}
name: tmp
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: controller
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: controller
namespace: sealed-secrets
spec:
ports:
- name: http
port: 80
targetPort: http
selector:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/name: controller
app.kubernetes.io/part-of: sealed-secrets
sessionAffinity: None
type: ClusterIP
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: controller-service-proxier
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: controller-service-proxier
namespace: sealed-secrets
rules:
- apiGroups:
- ''
resourceNames:
- 'http:controller:'
- controller
resources:
- services/proxy
verbs:
- create
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/instance: sealed-secrets-9ee46c
app.kubernetes.io/managed-by: kubespec
app.kubernetes.io/name: controller-service-proxier
app.kubernetes.io/part-of: sealed-secrets
app.kubernetes.io/version: 0.9.2
mz.com/owner: techops-sre
name: controller-service-proxier
namespace: sealed-secrets
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: controller-service-proxier
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:authenticated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment