package render import ( "bytes" "fmt" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/util/intstr" monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1" "example.com/monok8s/pkg/templates" ) const ( sshdName = "sshd" sshdConfigName = "sshd-authorized-keys" sshdNodePort = int32(30022) ) func RenderSSHDDeployments(namespace, authKeys string) (string, error) { vals := templates.LoadTemplateValuesFromEnv() labels := map[string]string{ "app.kubernetes.io/name": sshdName, "app.kubernetes.io/component": "host-access", "app.kubernetes.io/part-of": "monok8s", "app.kubernetes.io/managed-by": monov1alpha1.NodeControlName, } objs := []runtime.Object{ buildSSHDConfigMap(authKeys, namespace, labels), buildSSHDService(vals, namespace, labels), buildSSHDDeployment(vals, namespace, labels), } s := runtime.NewScheme() _ = corev1.AddToScheme(s) _ = rbacv1.AddToScheme(s) _ = appsv1.AddToScheme(s) serializer := json.NewYAMLSerializer(json.DefaultMetaFactory, s, s) var buf bytes.Buffer for i, obj := range objs { if i > 0 { if _, err := fmt.Fprintln(&buf, "---"); err != nil { return "", err } } if err := serializer.Encode(obj, &buf); err != nil { return "", err } } return buf.String(), nil } func buildSSHDConfigMap( authorizedKeys string, namespace string, labels map[string]string, ) *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "ConfigMap", }, ObjectMeta: metav1.ObjectMeta{ Name: sshdConfigName, Namespace: namespace, Labels: labels, }, Data: map[string]string{ "authorized_keys": authorizedKeys, }, } } func buildSSHDService( tVals templates.TemplateValues, namespace string, labels map[string]string, ) *corev1.Service { selectorLabels := map[string]string{ monov1alpha1.NodeControlKey: "true", "kubernetes.io/hostname": tVals.NodeName, } return &corev1.Service{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "Service", }, ObjectMeta: metav1.ObjectMeta{ Name: sshdName, Namespace: namespace, Labels: labels, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeNodePort, Selector: selectorLabels, Ports: []corev1.ServicePort{ { Name: "ssh", Protocol: corev1.ProtocolTCP, Port: 22, TargetPort: intstr.FromInt32(22), NodePort: sshdNodePort, }, }, }, } } func buildSSHDDeployment( tVals templates.TemplateValues, namespace string, labels map[string]string, ) *appsv1.Deployment { replicas := int32(1) selectorLabels := map[string]string{ monov1alpha1.NodeControlKey: "true", "kubernetes.io/hostname": tVals.NodeName, } podLabels := mergeStringMaps(labels, selectorLabels) runAsUser := int64(0) runAsNonRoot := false privileged := true allowPrivilegeEscalation := true readOnlyRootFilesystem := false return &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ APIVersion: "apps/v1", Kind: "Deployment", }, ObjectMeta: metav1.ObjectMeta{ Name: sshdName, Namespace: namespace, Labels: labels, }, Spec: appsv1.DeploymentSpec{ Replicas: &replicas, Selector: &metav1.LabelSelector{ MatchLabels: selectorLabels, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: podLabels, }, Spec: corev1.PodSpec{ HostPID: true, NodeSelector: selectorLabels, Containers: []corev1.Container{ { Name: sshdName, Image: "alpine:latest", Command: []string{ "/bin/sh", "-ceu", ` apk add --no-cache openssh-server mkdir -p /run/sshd mkdir -p /root/.ssh cp /authorized-keys/authorized_keys /root/.ssh/authorized_keys chmod 700 /root/.ssh chmod 600 /root/.ssh/authorized_keys ssh-keygen -A exec /usr/sbin/sshd \ -D \ -e \ -p 22 \ -o PermitRootLogin=prohibit-password \ -o PasswordAuthentication=no \ -o KbdInteractiveAuthentication=no \ -o PubkeyAuthentication=yes \ -o AuthorizedKeysFile=/root/.ssh/authorized_keys `, }, Ports: []corev1.ContainerPort{ { Name: "ssh", ContainerPort: 22, Protocol: corev1.ProtocolTCP, }, }, SecurityContext: &corev1.SecurityContext{ RunAsUser: &runAsUser, RunAsNonRoot: &runAsNonRoot, Privileged: &privileged, AllowPrivilegeEscalation: &allowPrivilegeEscalation, ReadOnlyRootFilesystem: &readOnlyRootFilesystem, }, Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("10m"), corev1.ResourceMemory: resource.MustParse("32Mi"), }, Limits: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("200m"), corev1.ResourceMemory: resource.MustParse("128Mi"), }, }, VolumeMounts: append( []corev1.VolumeMount{ { Name: "authorized-keys", MountPath: "/authorized-keys", ReadOnly: true, }, }, buildHostRootVolumeMounts()..., ), }, }, Volumes: append( []corev1.Volume{ { Name: "authorized-keys", VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ Name: sshdConfigName, }, DefaultMode: ptrInt32(0600), }, }, }, }, buildHostRootVolumes()..., ), }, }, }, } } func buildHostRootVolumeMounts() []corev1.VolumeMount { paths := []struct { name string mountPath string readOnly bool }{ {"host-bin", "/host/bin", true}, {"host-sbin", "/host/sbin", true}, {"host-lib", "/host/lib", true}, {"host-usr", "/host/usr", true}, {"host-etc", "/host/etc", false}, {"host-run", "/host/run", false}, {"host-proc", "/host/proc", false}, {"host-sys", "/host/sys", false}, {"host-dev", "/host/dev", false}, {"host-var", "/host/var", false}, } mounts := make([]corev1.VolumeMount, 0, len(paths)) for _, p := range paths { mounts = append(mounts, corev1.VolumeMount{ Name: p.name, MountPath: p.mountPath, ReadOnly: p.readOnly, }) } return mounts } func buildHostRootVolumes() []corev1.Volume { hostPathDir := corev1.HostPathDirectory paths := []struct { name string path string }{ {"host-bin", "/bin"}, {"host-sbin", "/sbin"}, {"host-lib", "/lib"}, {"host-usr", "/usr"}, {"host-etc", "/etc"}, {"host-run", "/run"}, {"host-proc", "/proc"}, {"host-sys", "/sys"}, {"host-dev", "/dev"}, // /var is an rbind mount in monok8s and may be private. // Mount the real backing path instead. {"host-var", "/data/var"}, } volumes := make([]corev1.Volume, 0, len(paths)) for _, p := range paths { volumes = append(volumes, corev1.Volume{ Name: p.name, VolumeSource: corev1.VolumeSource{ HostPath: &corev1.HostPathVolumeSource{ Path: p.path, Type: &hostPathDir, }, }, }) } return volumes } func ptrInt32(v int32) *int32 { return &v } func ptrHostPathType(v corev1.HostPathType) *corev1.HostPathType { return &v }