Skip to content

Commit

Permalink
SSH access: add bastion dockerfile, bastion-operator, cloud-init tena…
Browse files Browse the repository at this point in the history
…nt's key injection
  • Loading branch information
claudious96 committed Nov 28, 2020
1 parent 82612ad commit 7d8d5e3
Show file tree
Hide file tree
Showing 10 changed files with 364 additions and 8 deletions.
21 changes: 21 additions & 0 deletions infrastructure/ssh-bastion/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM alpine:3.12.1
RUN apk add --no-cache dumb-init openssh

# Create new user bastion with nologin
RUN adduser -D -s /sbin/nologin bastion
RUN passwd -u -d bastion

# Create host keys
RUN ssh-keygen -A

# Generate custom config for sshd
RUN (echo 'PasswordAuthentication no'; \
echo 'port 2222'; \
echo 'PubkeyAuthentication yes'; \
echo 'StrictModes no'; ) > /etc/ssh/sshd_config_custom

EXPOSE 2222

ENTRYPOINT ["/usr/bin/dumb-init", "--"]

CMD ["/usr/sbin/sshd", "-D", "-e", "-f", "/etc/ssh/sshd_config_custom"]
13 changes: 13 additions & 0 deletions operators/build/bastion-operator/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Build the manager binary
FROM golang:1.15 as builder
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go
COPY ./ /go/src/github.com/netgroup-polito/CrownLabs/operators/
WORKDIR /go/src/github.com/netgroup-polito/CrownLabs/operators/
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o controller ./cmd/bastion-operator/main.go
RUN cp controller /usr/bin/controller

FROM busybox
COPY --from=builder /usr/bin/controller /usr/bin/controller
USER 20000:20000
ENTRYPOINT [ "/usr/bin/controller" ]
83 changes: 83 additions & 0 deletions operators/cmd/bastion-operator/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"flag"
"os"

crownlabsv1alpha1 "github.com/netgroup-polito/CrownLabs/operators/api/v1alpha1"
"github.com/netgroup-polito/CrownLabs/operators/pkg/controllers"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
// +kubebuilder:scaffold:imports
)

var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)

func init() {
_ = clientgoscheme.AddToScheme(scheme)

_ = crownlabsv1alpha1.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}

func main() {
var metricsAddr string
var enableLeaderElection bool
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.Parse()

ctrl.SetLogger(zap.New(zap.UseDevMode(true)))

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
LeaderElection: enableLeaderElection,
LeaderElectionID: "95f0db32.crownlabs.polito.it",
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}

if err = (&controllers.BastionReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Bastion"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Bastion")
os.Exit(1)
}
// +kubebuilder:scaffold:builder

setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
16 changes: 16 additions & 0 deletions operators/deploy/bastion-operator/k8s-cluster-role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: crownlabs-bastion-operator
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","list","watch"]

- apiGroups: [""]
resources: ["secrets","services","events"]
verbs: ["get","list","watch","create"]

- apiGroups: ["crownlabs.polito.it"]
resources: ["tenants"]
verbs: ["list","watch"]
97 changes: 97 additions & 0 deletions operators/deploy/bastion-operator/k8s-manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: bastion-operator
namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: crownlabs-bastion-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: crownlabs-bastion-operator
subjects:
- kind: ServiceAccount
name: bastion-operator
namespace: default

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ssh-bastion
namespace: default
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: ssh-bastion
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
kubectl.kubernetes.io/restartedAt: "2020-09-12T12:59:32Z"
creationTimestamp: null
labels:
app: ssh-bastion
spec:
serviceAccountName: bastion-operator
containers:
- name: sidecar
image: crownlabs/ssh-bastion-operator:latest
imagePullPolicy: IfNotPresent
command: ["/usr/bin/controller"]
resources: {}
volumeMounts:
- name: authorized-keys
mountPath: /auth-keys-vol
securityContext:
allowPrivilegeEscalation: false
runAsUser: 20000
runAsGroup: 20000
privileged: false
- name: bastion
args: ["-D", "-e", "-f","/etc/ssh/sshd_config_custom"]
command: ["/usr/sbin/sshd"]
image: crownlabs/ssh-bastion:latest
imagePullPolicy: Always
ports:
- containerPort: 2222
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /home/bastion/.ssh
name: authorized-keys
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- name: authorized-keys
emptyDir: {}

---
apiVersion: v1
kind: Service
metadata:
name: ssh-bastion
spec:
ports:
- port: 2222
targetPort: 2222
name: ssh
selector:
app: ssh-bastion
type: LoadBalancer
86 changes: 86 additions & 0 deletions operators/pkg/controllers/bastion_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
"context"
"os"
"strings"

"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

crownlabsalpha1 "github.com/netgroup-polito/CrownLabs/operators/api/v1alpha1"
)

// BastionReconciler reconciles a Bastion object
type BastionReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}

// +kubebuilder:rbac:groups=crownlabs.polito.it,resources=tenants,verbs=list

func (r *BastionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
log := r.Log.WithValues("bastion", req.NamespacedName)
log.Info("reconciling bastion")

// Get tenants resources
var list crownlabsalpha1.TenantList
if err := r.List(ctx, &list); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Collect public keys of tenants
keys := make([]string, 0, len(list.Items))
for _, tenant := range list.Items {
keys = append(keys, tenant.Spec.PublicKeys...)
}

authorizedKeys := strings.Join(keys[:], "\n")

f, err := os.Create("/auth-keys-vol/authorized_keys")
if err != nil {
log.Error(err, "unable to create the file authorized_keys")
return ctrl.Result{}, nil
}
_, err = f.WriteString(authorizedKeys)
if err != nil {
log.Error(err, "unable to write to authorized_keys")
f.Close()
return ctrl.Result{}, nil
}

err = f.Close()
if err != nil {
log.Error(err, "unable to close the file authorized_keys")
return ctrl.Result{}, nil
}

return ctrl.Result{}, nil
}

func (r *BastionReconciler) SetupWithManager(mgr ctrl.Manager) error {

return ctrl.NewControllerManagedBy(mgr).
For(&crownlabsalpha1.Tenant{}).
Complete(r)
}
10 changes: 8 additions & 2 deletions operators/pkg/controllers/logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@ func (r *LabInstanceReconciler) CreateEnvironment(labInstance *crownlabsv1alpha2
if err != nil {
log.Error(err, "unable to get Webdav Credentials")
} else {
log.Info("Webdav secrets obtained. Building cloud-init script." + labInstance.Name)
log.Info("Webdav secrets obtained. Getting public keys." + labInstance.Name)
}
secret := instance_creation.CreateCloudInitSecret(name, namespace, user, password, r.NextcloudBaseUrl)
var publicKeys []string
if err := instance_creation.GetPublicKeys(r.Client, ctx, log, labInstance.Spec.Tenant.Name, labInstance.Spec.Tenant.Namespace, &publicKeys); err != nil {
log.Error(err, "unable to get public keys")
} else {
log.Info("Public keys obtained. Building cloud-init script." + labInstance.Name)
}
secret := instance_creation.CreateCloudInitSecret(name, namespace, user, password, r.NextcloudBaseUrl, publicKeys)
secret.SetOwnerReferences(labiOwnerRef)
if err := instance_creation.CreateOrUpdate(r.Client, ctx, log, secret); err != nil {
setLabInstanceStatus(r, ctx, log, "Could not create secret "+secret.Name+" in namespace "+secret.Namespace, "Warning", "SecretNotCreated", labInstance, "", "")
Expand Down
13 changes: 8 additions & 5 deletions operators/pkg/instance-creation/cloud-init.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ type cloudInitConfig struct {
ID0 interface{} `yaml:"id0"`
Dhcp4 bool `yaml:"dhcp4"`
} `yaml:"network"`
Mounts [][]string `yaml:"mounts"`
WriteFiles []writeFile `yaml:"write_files"`
Mounts [][]string `yaml:"mounts"`
WriteFiles []writeFile `yaml:"write_files"`
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
}

func createUserdata(nextUsername string, nextPassword string, nextCloudBaseUrl string) map[string]string {
func createUserdata(nextUsername string, nextPassword string, nextCloudBaseUrl string, publicKeys []string) map[string]string {
var Userdata cloudInitConfig

Userdata.Network.Version = 2
Expand All @@ -41,6 +42,7 @@ func createUserdata(nextUsername string, nextPassword string, nextCloudBaseUrl s
Permissions: "0600"},
// New write_files should be added here as []writeFile
}
Userdata.SSHAuthorizedKeys = publicKeys

out, _ := yaml.Marshal(Userdata)

Expand All @@ -49,7 +51,7 @@ func createUserdata(nextUsername string, nextPassword string, nextCloudBaseUrl s
return map[string]string{"userdata": headerComment + string(out)}
}

func CreateCloudInitSecret(name string, namespace string, nextUsername string, nextPassword string, nextCloudBaseUrl string) v1.Secret {
func CreateCloudInitSecret(name string, namespace string, nextUsername string, nextPassword string, nextCloudBaseUrl string, publicKeys []string) v1.Secret {
secret := v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "v1",
Expand All @@ -63,7 +65,8 @@ func CreateCloudInitSecret(name string, namespace string, nextUsername string, n
StringData: createUserdata(
nextUsername,
nextPassword,
nextCloudBaseUrl),
nextCloudBaseUrl,
publicKeys),
Type: v1.SecretTypeOpaque,
}

Expand Down
Loading

0 comments on commit 7d8d5e3

Please sign in to comment.