Last active
June 1, 2021 04:38
-
-
Save abursavich/03a48e23d4c1c5b03f6406b8922324a7 to your computer and use it in GitHub Desktop.
kubespec usage example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | |
) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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