Commit 21cb2142 authored by Geoff Simmons's avatar Geoff Simmons

Refactor RBAC for the controller and viking service.

If the controller is watching resources in all namespaces (CLI option namespace
is not set, helm value vikingController.namespace is undefined or empty), then
define a ClusterRole as we do now.

In the helm chart we use the prefix "viking.uplex.de:" in the ClusterRole's
name, since ClusterRoles are not namespaced.

If the controller is watching one namespace (CLI option namespace, helm value
vikingController.namespace are set to the namespace), define a Role in the
namespace, and a RoleBinding to connect it to the ServiceAccount. Then the
restriction to the namespace is enforced by RBAC.

For the viking service (Varnish/haproxy-as-Ingress implementation): in place
of the ClusterRole and ClusterRoleBinding we have now, define a Role and
RoleBinding in the namespace in which the Pods run. This enforces the
restriction to the namespace. RBAC is needed to read TLS Secrets, which must
be in the same namespace.

This means that the k8s-crt-dnldr running in the haproxy container must be
invoked with the namespace CLI arg. For that, we use the downward API to
pass POD_NAMESPACE into the container.

The namespace example is adjusted for these changes, and a Makefile drives
the tests, using both helm and kubectl.

The archtiectures and varnish_pod_template tests have also been adjusted,
although for now these are run only with kubectl.
parent cffa3e49
{{ if not .Values.vikingController.namespace -}}
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
......@@ -6,7 +7,7 @@ metadata:
helm.sh/chart: {{ template "viking-controller.chart" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ template "viking-controller.fullname" . }}
name: viking.uplex.de:{{ template "viking-controller.fullname" . }}
rules:
- apiGroups:
- ""
......@@ -68,4 +69,4 @@ rules:
- backendconfigs/status
verbs:
- update
{{- end }}
{{ if not .Values.vikingController.namespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
......@@ -13,5 +14,6 @@ subjects:
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: {{ template "viking-controller.fullname" . }}
name: viking.uplex.de:{{ template "viking-controller.fullname" . }}
apiGroup: rbac.authorization.k8s.io
{{- end }}
{{ if .Values.vikingController.namespace }}
{{ if ne .Values.vikingController.namespace .Release.Namespace }}
{{ fail "Value vikingController.namespace must match release namespace" }}
{{ end -}}
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
app.kubernetes.io/name: {{ template "viking-controller.name" . }}
helm.sh/chart: {{ template "viking-controller.chart" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ template "viking-controller.fullname" . }}
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- list
- watch
- get
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- ingress.varnish-cache.org
resources:
- varnishconfigs
- backendconfigs
verbs:
- list
- watch
- get
- apiGroups:
- "ingress.varnish-cache.org"
resources:
- varnishconfigs/status
- backendconfigs/status
verbs:
- update
{{- end }}
{{ if not .Values.vikingController.namespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/name: {{ template "viking-controller.name" . }}
helm.sh/chart: {{ template "viking-controller.chart" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ template "viking-controller.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ template "viking-controller.fullname" . }}
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: {{ template "viking-controller.fullname" . }}
apiGroup: rbac.authorization.k8s.io
{{- end }}
......@@ -146,6 +146,10 @@ spec:
secretKeyRef:
name: {{ template "viking-service.admin-secret-name" . }}
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
{{- if .Values.vikingService.haproxy.extraEnvs }}
{{- toYaml .Values.vikingService.haproxy.extraEnvs | nindent 12 }}
{{- end }}
......
kind: ClusterRole
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
......
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/name: {{ template "viking-service.name" . }}
......@@ -12,6 +12,6 @@ subjects:
name: {{ template "viking-service.fullname" . }}
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
kind: Role
name: {{ template "viking-service.fullname" . }}
apiGroup: rbac.authorization.k8s.io
......@@ -32,7 +32,7 @@ program api
no option start-on-reload
program crt-dnldr
command /usr/bin/k8s-crt-dnldr --addr unix@/run/offload/crt-dnldr.sock --base /etc/ssl/private -pemGid 998 -udsGid 998 -udsMode 0660
command /usr/bin/k8s-crt-dnldr --addr unix@/run/offload/crt-dnldr.sock --base /etc/ssl/private -pemGid 998 -udsGid 998 -udsMode 0660 --namespace %%POD_NAMESPACE%%
no option start-on-reload
frontend readiness
......
......@@ -3,6 +3,6 @@
set -e
set -u
/bin/sed "s/%%SECRET_DATAPLANEAPI%%/${SECRET_DATAPLANEAPI}/g" /etc/haproxy/haproxy.cfg > /run/haproxy/haproxy.cfg
/bin/sed -e "s/%%SECRET_DATAPLANEAPI%%/${SECRET_DATAPLANEAPI}/g" -e "s/%%POD_NAMESPACE%%/${POD_NAMESPACE}/g" /etc/haproxy/haproxy.cfg > /run/haproxy/haproxy.cfg
exec /usr/sbin/haproxy -f /run/haproxy/haproxy.cfg "$@"
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress-controller
name: viking.uplex.de:controller
rules:
- apiGroups:
- ""
......@@ -74,5 +74,5 @@ subjects:
namespace: kube-system
roleRef:
kind: ClusterRole
name: varnish-ingress-controller
name: viking.uplex.de:controller
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress
......@@ -12,7 +12,7 @@ rules:
- list
- watch
---
kind: ClusterRoleBinding
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress
......@@ -21,6 +21,6 @@ subjects:
namespace: default
name: varnish-ingress
roleRef:
kind: ClusterRole
kind: Role
name: varnish-ingress
apiGroup: rbac.authorization.k8s.io
......@@ -67,6 +67,10 @@ spec:
secretKeyRef:
name: adm-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: kube-system
name: varnish-ingress
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: cafe
name: varnish-ingress
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: varnish-ingress
namespace: kube-system
---
kind: ClusterRoleBinding
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress-cluster-ns-system
name: varnish-ingress
namespace: kube-system
subjects:
- kind: ServiceAccount
name: varnish-ingress
namespace: kube-system
roleRef:
kind: ClusterRole
kind: Role
name: varnish-ingress
apiGroup: rbac.authorization.k8s.io
---
......@@ -23,15 +54,16 @@ metadata:
name: varnish-ingress
namespace: cafe
---
kind: ClusterRoleBinding
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress-cluster-ns-coffee
name: varnish-ingress
namespace: cafe
subjects:
- kind: ServiceAccount
name: varnish-ingress
namespace: cafe
roleRef:
kind: ClusterRole
kind: Role
name: varnish-ingress
apiGroup: rbac.authorization.k8s.io
......@@ -66,6 +66,10 @@ spec:
secretKeyRef:
name: adm-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
......@@ -66,6 +66,10 @@ spec:
secretKeyRef:
name: adm-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: kube-system
name: varnish-ingress
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: varnish-ingress
namespace: kube-system
---
kind: ClusterRoleBinding
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress-clusterwide-example
name: varnish-ingress
namespace: kube-system
subjects:
- kind: ServiceAccount
name: varnish-ingress
namespace: kube-system
roleRef:
kind: ClusterRole
kind: Role
name: varnish-ingress
apiGroup: rbac.authorization.k8s.io
......@@ -66,6 +66,10 @@ spec:
secretKeyRef:
name: adm-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: cafe
name: varnish-ingress
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: varnish-ingress
namespace: cafe
---
kind: ClusterRoleBinding
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress-multi-controller-example
name: varnish-ingress
namespace: cafe
subjects:
- kind: ServiceAccount
name: varnish-ingress
namespace: cafe
roleRef:
kind: ClusterRole
kind: Role
name: varnish-ingress
apiGroup: rbac.authorization.k8s.io
......@@ -68,6 +68,10 @@ spec:
secretKeyRef:
name: coffee-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
......@@ -68,6 +68,10 @@ spec:
secretKeyRef:
name: tea-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: varnish-ingress
namespace: cafe
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: varnish-ingress
namespace: cafe
---
kind: ClusterRoleBinding
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress-multi-varnish-example
name: varnish-ingress
namespace: cafe
subjects:
- kind: ServiceAccount
name: varnish-ingress
namespace: cafe
roleRef:
kind: ClusterRole
kind: Role
name: varnish-ingress
apiGroup: rbac.authorization.k8s.io
......@@ -68,6 +68,10 @@ spec:
secretKeyRef:
name: coffee-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
......@@ -68,6 +68,10 @@ spec:
secretKeyRef:
name: tea-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
# Copyright (c) 2020 UPLEX Nils Goroll Systemoptimierung
# All rights reserved
#
# Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
# GNU make is required.
mkpath := $(abspath $(lastword $(MAKEFILE_LIST)))
mkdir := $(dir $(mkpath))
CHARTDIR=$(mkdir)/../../charts
# For the klarlack image: make VARNISH=klarlack ...
ifndef VARNISH
VARNISH=varnish
endif
CI_REPO_PFX=registry.gitlab.com/uplex/varnish/k8s-ingress/varnish-ingress
# For tests using the local docker registry: make TEST=local ...
# For tests using images from the CI pipeline: make TEST=ci ...
ifeq ($(TEST),local)
CONTROLLER_IMAGE=varnish-ingress/controller
CONTROLLER_TAG=latest
VARNISH_IMAGE=varnish-ingress/$(VARNISH)
VARNISH_TAG=latest
HAPROXY_IMAGE=varnish-ingress/haproxy
HAPROXY_TAG=latest
else ifeq ($(TEST),ci)
CONTROLLER_IMAGE=$(CI_REPO_PFX)/controller
CONTROLLER_TAG=master
VARNISH_IMAGE=$(CI_REPO_PFX)/$(VARNISH)
VARNISH_TAG=master
HAPROXY_IMAGE=$(CI_REPO_PFX)/haproxy
HAPROXY_TAG=master
endif
# If not specified, pull the latest "official" images from dockerhub.
LATEST=0.0.2
ifndef CONTROLLER_IMAGE
CONTROLLER_IMAGE=uplex/viking-controller
endif
ifndef CONTROLLER_TAG
CONTROLLER_TAG=$(LATEST)
endif
ifndef VARNISH_IMAGE
VARNISH_IMAGE=uplex/viking-$(VARNISH)
endif
ifndef VARNISH_TAG
VARNISH_TAG=$(LATEST)
endif
ifndef HAPROXY_IMAGE
HAPROXY_IMAGE=uplex/viking-haproxy
endif
ifndef HAPROXY_TAG
HAPROXY_TAG=$(LATEST)
endif
all: deploy
deploy-helm:
@kubectl apply -f namespace.yaml
@helm install viking-controller-ns $(CHARTDIR)/viking-controller \
--values values-controller.yaml --namespace varnish-ingress \
--set vikingController.image.repository=$(CONTROLLER_IMAGE) \
--set vikingController.image.tag=$(CONTROLLER_TAG)
@helm install viking-service-ns $(CHARTDIR)/viking-service \
--values values-varnish.yaml --namespace varnish-ingress \
--set vikingService.varnish.image.repository=$(VARNISH_IMAGE) \
--set vikingService.varnish.image.tag=$(VARNISH_TAG) \
--set vikingService.haproxy.image.repository=$(HAPROXY_IMAGE) \
--set vikingService.haproxy.image.tag=$(HAPROXY_TAG)
@helm install viking-ingress $(CHARTDIR)/viking-test-app \
--values values.yaml --namespace varnish-ingress
deploy-kubectl:
@kubectl apply -f namespace.yaml
@kubectl apply -f serviceaccount.yaml
@kubectl apply -f rbac.yaml
@kubectl apply -f adm-secret.yaml
@kubectl apply -f varnish.yaml
@kubectl apply -f admin-svc.yaml
@kubectl apply -f nodeport.yaml
@kubectl apply -f controller.yaml
@kubectl apply -f cafe.yaml
@kubectl apply -f cafe-ingress.yaml
deploy:
ifeq ($(DEPLOY),kubectl)
deploy: deploy-kubectl
else
deploy: deploy-helm
endif
# TESTOPTS are passed to varnishtest, e.g.: make TESTOPTS=-v verify
verify:
$(mkdir)/verify.sh
undeploy-helm:
@helm uninstall viking-ingress --namespace varnish-ingress
@helm uninstall viking-service-ns --namespace varnish-ingress
@helm uninstall viking-controller-ns --namespace varnish-ingress
@kubectl delete -f namespace.yaml
undeploy-kubectl:
@kubectl delete -f cafe-ingress.yaml
@kubectl delete -f cafe.yaml
@kubectl delete -f controller.yaml
@kubectl delete -f nodeport.yaml
@kubectl delete -f admin-svc.yaml
@kubectl delete -f varnish.yaml
@kubectl delete -f adm-secret.yaml
@kubectl delete -f rbac.yaml
@kubectl delete -f serviceaccount.yaml
@kubectl delete -f namespace.yaml
undeploy:
ifeq ($(DEPLOY),kubectl)
undeploy: undeploy-kubectl
else
undeploy: undeploy-helm
endif
.PHONY: all $(MAKECMDGOALS)
#! /bin/bash -ex
kubectl apply -f ns-and-sa.yaml
kubectl apply -f namespace.yaml
kubectl apply -f serviceaccount.yaml
kubectl apply -f rbac.yaml
......
apiVersion: v1
kind: Namespace
metadata:
name: varnish-ingress
kind: ClusterRoleBinding
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: varnish-ingress
name: varnish-ingress-controller
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- list
- watch
- get
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- ingress.varnish-cache.org
resources:
- varnishconfigs
- backendconfigs
verbs:
- list
- watch
- get
- apiGroups:
- "ingress.varnish-cache.org"
resources:
- varnishconfigs/status
- backendconfigs/status
verbs:
- update
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: varnish-ingress
name: varnish-ingress
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress-controller-namespace-example
namespace: varnish-ingress
name: viking-controller
subjects:
- kind: ServiceAccount
name: varnish-ingress-controller
namespace: varnish-ingress
roleRef:
kind: ClusterRole
kind: Role
name: varnish-ingress-controller
apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRoleBinding
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: varnish-ingress-namespace-example
namespace: varnish-ingress
name: viking-service
subjects:
- kind: ServiceAccount
name: varnish-ingress
namespace: varnish-ingress
roleRef:
kind: ClusterRole
kind: Role
name: varnish-ingress
apiGroup: rbac.authorization.k8s.io
apiVersion: v1
kind: Namespace
metadata:
name: varnish-ingress
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: varnish-ingress-controller
......
......@@ -16,4 +16,6 @@ kubectl delete -f adm-secret.yaml
kubectl delete -f rbac.yaml
kubectl delete -f ns-and-sa.yaml
kubectl delete -f serviceaccount.yaml
kubectl delete -f namespace.yaml
vikingController:
## Name of the ingress class to route through this controller
##
ingressClass: varnish
## Only listen for resources in this namespace (default all)
namespace: varnish-ingress
# labels to add to the pod container metadata
podLabels:
app: varnish-ingress-controller
nameOverride: varnish-ingress
fullnameOverride: varnish-ingress
vikingService:
varnish:
extraArgs: []
## Name of the ingress class to route through this controller
##
ingressClass: varnish
# labels to add to the pod container metadata
podLabels:
app: varnish-ingress
apps:
coffee:
image: nginxdemos/hello:plain-text
replicas: 2
tea:
image: nginxdemos/hello:plain-text
replicas: 3
ingress:
name: cafe-ingress
rules:
- host: cafe.example.com
paths:
- path: /tea
app: tea
- path: /coffee
app: coffee
......@@ -66,6 +66,10 @@ spec:
secretKeyRef:
name: adm-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
......@@ -76,6 +76,10 @@ spec:
secretKeyRef:
name: adm-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
......@@ -105,6 +105,10 @@ spec:
secretKeyRef:
name: adm-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: VARNISH_READY_PORT
value: "8000"
livenessProbe:
......
......@@ -72,6 +72,10 @@ spec:
secretKeyRef:
name: adm-secret
key: dataplaneapi
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
exec:
command:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment