Skip to content

Commit

Permalink
Merge pull request #8 from observatorium/make_namespace-provisioner_m…
Browse files Browse the repository at this point in the history
…ore_secure

configuration: make namespace-provisioner more secure
  • Loading branch information
squat authored Feb 9, 2024
2 parents c1f7cdd + aedb63c commit 032ef56
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 52 deletions.
17 changes: 2 additions & 15 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ import (
"github.com/prometheus/client_golang/prometheus"
flag "github.com/spf13/pflag"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/deprecated/scheme"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
Expand Down Expand Up @@ -59,7 +56,7 @@ func Main() error {
listenInternal := flag.String("listen-internal", ":9090", "The address at which to to serve the internal API.")
logLevel := flag.String("log-level", logLevelInfo, fmt.Sprintf("Log level to use. Possible values: %s", availableLogLevels))
apiServer := flag.String("server", "kubernetes", "The address of the Kubernetes API server to use in generated kubeconfigs.")
rolePath := flag.String("role", "", "The path to a file containing a Kubernetes RBAC role.")
clusterRole := flag.String("cluster-role", "", "The of a Kubernetes ClusterRole to bind to ServiceAccounts in created Namespaces.")
prefix := flag.String("prefix", "np", "The prefix to use for Namespace names.")
selector := flag.String("selector", "controller.observatorium.io=namespace-selector", "The label selector to use to select resources.")
token := flag.String("token", "", "The token to require for authentication with the API.")
Expand Down Expand Up @@ -99,16 +96,6 @@ func Main() error {
prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}),
)

rawRole, err := os.ReadFile(*rolePath)
if err != nil {
return fmt.Errorf("failed to read Role %q: %w", *rolePath, err)
}
role := &rbacv1.Role{}
if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), rawRole, role); err != nil {
return fmt.Errorf("failed to decode Role: %w", err)
}
role.Name = np

ls, err := labels.ConvertSelectorToLabelsMap(*selector)
if err != nil {
return fmt.Errorf("failed to parse label selector: %w", err)
Expand Down Expand Up @@ -148,7 +135,7 @@ func Main() error {
if err != nil {
return err
}
h := newHander(l, r, c, factory, ls, apiServerURL, *prefix, role, *token, *ttl)
h := newHander(l, r, c, factory, ls, apiServerURL, *prefix, *clusterRole, *token, *ttl)
s := http.Server{Addr: *listen, Handler: h}

// Start the API server.
Expand Down
8 changes: 8 additions & 0 deletions manifests/grant.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-provisioner-grant
labels:
app.kubernetes.io/name: namespace-provisioner-grant
app.kubernetes.io/part-of: namespace-provisioner
rules: []
140 changes: 140 additions & 0 deletions manifests/namespace-provisioner.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
apiVersion: v1
kind: Namespace
metadata:
name: namespace-provisioner
labels:
app.kubernetes.io/name: namespace-provisioner
app.kubernetes.io/part-of: namespace-provisioner
---
apiVersion: v1
kind: Secret
metadata:
name: namespace-provisioner
namespace: namespace-provisioner
labels:
app.kubernetes.io/name: namespace-provisioner
app.kubernetes.io/part-of: namespace-provisioner
stringData:
token: PASSWORD
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: namespace-provisioner
namespace: namespace-provisioner
labels:
app.kubernetes.io/name: namespace-provisioner
app.kubernetes.io/part-of: namespace-provisioner
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-provisioner
labels:
app.kubernetes.io/name: namespace-provisioner
app.kubernetes.io/part-of: namespace-provisioner
rules:
- apiGroups:
- ""
resources:
- namespaces
verbs:
- create
- delete
- list
- watch
- apiGroups:
- ""
resources:
- secrets
resourceNames:
- namespace-provisioner
verbs:
- get
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- create
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- create
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterroles
verbs:
- bind
resourceNames:
- namespace-provisioner-grant
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: namespace-provisioner
labels:
app.kubernetes.io/name: namespace-provisioner
app.kubernetes.io/part-of: namespace-provisioner
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: namespace-provisioner
subjects:
- kind: ServiceAccount
namespace: namespace-provisioner
name: namespace-provisioner
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: namespace-provisioner
namespace: namespace-provisioner
labels:
app.kubernetes.io/name: namespace-provisioner
app.kubernetes.io/part-of: namespace-provisioner
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: namespace-provisioner
app.kubernetes.io/part-of: namespace-provisioner
template:
metadata:
labels:
app.kubernetes.io/name: namespace-provisioner
app.kubernetes.io/part-of: namespace-provisioner
spec:
serviceAccountName: namespace-provisioner
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: namespace-provisioner
image: quay.io/observatorium/namespace-provisioner
imagePullPolicy: IfNotPresent
args:
- --listen=:8080
- --listen-internal=:9090
- --cluster-role=namespace-provisioner-grant
- --token=$(TOKEN)
env:
- name: TOKEN
valueFrom:
secretKeyRef:
name: namespace-provisioner
key: token
ports:
- containerPort: 8080
name: http
- containerPort: 9090
name: metrics
16 changes: 0 additions & 16 deletions role.yaml

This file was deleted.

26 changes: 5 additions & 21 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ type handler struct {
logger log.Logger
apiServerURL *url.URL
prefix string
role *rbacv1.Role
clusterRole string
ttl time.Duration

duration *prometheus.HistogramVec
}

func newHander(l log.Logger, r prometheus.Registerer, c kubernetes.Interface, factory informers.SharedInformerFactory, ls map[string]string, apiServerURL *url.URL, prefix string, role *rbacv1.Role, token string, ttl time.Duration) http.Handler {
func newHander(l log.Logger, r prometheus.Registerer, c kubernetes.Interface, factory informers.SharedInformerFactory, ls map[string]string, apiServerURL *url.URL, prefix string, clusterRole string, token string, ttl time.Duration) http.Handler {
if l == nil {
l = log.NewNopLogger()
}
Expand All @@ -63,7 +63,7 @@ func newHander(l log.Logger, r prometheus.Registerer, c kubernetes.Interface, fa
labels: ls,
logger: l,
apiServerURL: apiServerURL,
role: role,
clusterRole: clusterRole,
prefix: prefix,
ttl: ttl,

Expand Down Expand Up @@ -148,14 +148,6 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
return
}

role := h.role.DeepCopy()
role.Namespace = namespace
role.ObjectMeta.Labels = h.labels
if _, err := h.c.RbacV1().Roles(namespace).Create(r.Context(), role, metav1.CreateOptions{}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

rb := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: np,
Expand All @@ -164,8 +156,8 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
},
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: role.Kind,
Name: role.GetName(),
Kind: "ClusterRole",
Name: h.clusterRole,
},
Subjects: []rbacv1.Subject{{
Kind: "ServiceAccount",
Expand All @@ -180,14 +172,6 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
}

// Create Kubeconfig

// TODO: Use an informer or something proper as this might race - famous last words
sa, err = h.c.CoreV1().ServiceAccounts(namespace).Get(r.Context(), sa.Name, metav1.GetOptions{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

se, err = h.c.CoreV1().Secrets(namespace).Get(r.Context(), np, metav1.GetOptions{})
if err != nil {
msg := "no secret for service account"
Expand Down

0 comments on commit 032ef56

Please sign in to comment.