core -> metropolis
Smalltown is now called Metropolis!
This is the first commit in a series of cleanup commits that prepare us
for an open source release. This one just some Bazel packages around to
follow a stricter directory layout.
All of Metropolis now lives in `//metropolis`.
All of Metropolis Node code now lives in `//metropolis/node`.
All of the main /init now lives in `//m/n/core`.
All of the Kubernetes functionality/glue now lives in `//m/n/kubernetes`.
Next steps:
- hunt down all references to Smalltown and replace them appropriately
- narrow down visibility rules
- document new code organization
- move `//build/toolchain` to `//monogon/build/toolchain`
- do another cleanup pass between `//golibs` and
`//monogon/node/{core,common}`.
- remove `//delta` and `//anubis`
Fixes T799.
Test Plan: Just a very large refactor. CI should help us out here.
Bug: T799
X-Origin-Diff: phab/D667
GitOrigin-RevId: 6029b8d4edc42325d50042596b639e8b122d0ded
diff --git a/metropolis/node/kubernetes/reconciler/BUILD.bazel b/metropolis/node/kubernetes/reconciler/BUILD.bazel
new file mode 100644
index 0000000..d8f2db6
--- /dev/null
+++ b/metropolis/node/kubernetes/reconciler/BUILD.bazel
@@ -0,0 +1,38 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "go_default_library",
+ srcs = [
+ "reconciler.go",
+ "resources_csi.go",
+ "resources_podsecuritypolicy.go",
+ "resources_rbac.go",
+ "resources_runtimeclass.go",
+ "resources_storageclass.go",
+ ],
+ importpath = "git.monogon.dev/source/nexantic.git/metropolis/node/kubernetes/reconciler",
+ visibility = ["//metropolis/node:__subpackages__"],
+ deps = [
+ "//metropolis/node/common/supervisor:go_default_library",
+ "@io_k8s_api//core/v1:go_default_library",
+ "@io_k8s_api//node/v1beta1:go_default_library",
+ "@io_k8s_api//policy/v1beta1:go_default_library",
+ "@io_k8s_api//rbac/v1:go_default_library",
+ "@io_k8s_api//storage/v1:go_default_library",
+ "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
+ "@io_k8s_client_go//kubernetes:go_default_library",
+ ],
+)
+
+go_test(
+ name = "go_default_test",
+ srcs = ["reconciler_test.go"],
+ embed = [":go_default_library"],
+ deps = [
+ "@io_k8s_api//node/v1beta1:go_default_library",
+ "@io_k8s_api//policy/v1beta1:go_default_library",
+ "@io_k8s_api//rbac/v1:go_default_library",
+ "@io_k8s_api//storage/v1:go_default_library",
+ "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
+ ],
+)
diff --git a/metropolis/node/kubernetes/reconciler/reconciler.go b/metropolis/node/kubernetes/reconciler/reconciler.go
new file mode 100644
index 0000000..9c5ba4e
--- /dev/null
+++ b/metropolis/node/kubernetes/reconciler/reconciler.go
@@ -0,0 +1,163 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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.
+
+// The reconciler ensures that a base set of K8s resources is always available in the cluster. These are necessary to
+// ensure correct out-of-the-box functionality. All resources containing the smalltown.com/builtin=true label are assumed
+// to be managed by the reconciler.
+// It currently does not revert modifications made by admins, it is planned to create an admission plugin prohibiting
+// such modifications to resources with the smalltown.com/builtin label to deal with that problem. This would also solve a
+// potential issue where you could delete resources just by adding the smalltown.com/builtin=true label.
+package reconciler
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+
+ "git.monogon.dev/source/nexantic.git/metropolis/node/common/supervisor"
+)
+
+// Sad workaround for all the pointer booleans in K8s specs
+func True() *bool {
+ val := true
+ return &val
+}
+func False() *bool {
+ val := false
+ return &val
+}
+
+const (
+ // BuiltinLabelKey is used as a k8s label to mark built-in objects (ie., managed by the reconciler)
+ BuiltinLabelKey = "smalltown.com/builtin"
+ // BuiltinLabelValue is used as a k8s label value, under the BuiltinLabelKey key.
+ BuiltinLabelValue = "true"
+ // BuiltinRBACPrefix is used to prefix all built-in objects that are part of the rbac/v1 API (eg.
+ // {Cluster,}Role{Binding,} objects). This corresponds to the colon-separated 'namespaces' notation used by
+ // Kubernetes system (system:) objects.
+ BuiltinRBACPrefix = "smalltown:"
+)
+
+// builtinLabels makes a kubernetes-compatible label dictionary (key->value) that is used to mark objects that are
+// built-in into Smalltown (ie., managed by the reconciler). These are then subsequently retrieved by listBuiltins.
+// The extra argument specifies what other labels are to be merged into the the labels dictionary, for convenience. If
+// nil or empty, no extra labels will be applied.
+func builtinLabels(extra map[string]string) map[string]string {
+ l := map[string]string{
+ BuiltinLabelKey: BuiltinLabelValue,
+ }
+ if extra != nil {
+ for k, v := range extra {
+ l[k] = v
+ }
+ }
+ return l
+}
+
+// listBuiltins returns a k8s client ListOptions structure that allows to retrieve all objects that are built-in into
+// Smalltown currently present in the API server (ie., ones that are to be managed by the reconciler). These are created
+// by applying builtinLabels to their metadata labels.
+var listBuiltins = meta.ListOptions{
+ LabelSelector: fmt.Sprintf("%s=%s", BuiltinLabelKey, BuiltinLabelValue),
+}
+
+// builtinRBACName returns a name that is compatible with colon-delimited 'namespaced' objects, a la system:*.
+// These names are to be used by all builtins created as part of the rbac/v1 Kubernetes API.
+func builtinRBACName(name string) string {
+ return BuiltinRBACPrefix + name
+}
+
+// resource is a type of resource to be managed by the reconciler. All builti-ins/reconciled objects must implement
+// this interface to be managed correctly by the reconciler.
+type resource interface {
+ // List returns a list of names of objects current present on the target (ie. k8s API server).
+ List(ctx context.Context) ([]string, error)
+ // Create creates an object on the target. The el interface{} argument is the black box object returned by the
+ // Expected() call.
+ Create(ctx context.Context, el interface{}) error
+ // Delete delete an object, by name, from the target.
+ Delete(ctx context.Context, name string) error
+ // Expected returns a map of all objects expected to be present on the target. The keys are names (which must
+ // correspond to the names returned by List() and used by Delete(), and the values are blackboxes that will then
+ // be passed to the Create() call if their corresponding key (name) does not exist on the target.
+ Expected() map[string]interface{}
+}
+
+func allResources(clientSet kubernetes.Interface) map[string]resource {
+ return map[string]resource{
+ "psps": resourcePodSecurityPolicies{clientSet},
+ "clusterroles": resourceClusterRoles{clientSet},
+ "clusterrolebindings": resourceClusterRoleBindings{clientSet},
+ "storageclasses": resourceStorageClasses{clientSet},
+ "csidrivers": resourceCSIDrivers{clientSet},
+ "runtimeclasses": resourceRuntimeClasses{clientSet},
+ }
+}
+
+func Run(clientSet kubernetes.Interface) supervisor.Runnable {
+ return func(ctx context.Context) error {
+ log := supervisor.Logger(ctx)
+ resources := allResources(clientSet)
+ t := time.NewTicker(10 * time.Second)
+ reconcileAll := func() {
+ for name, resource := range resources {
+ if err := reconcile(ctx, resource); err != nil {
+ log.Warningf("Failed to reconcile built-in resources %s: %v", name, err)
+ }
+ }
+ }
+ supervisor.Signal(ctx, supervisor.SignalHealthy)
+ reconcileAll()
+ for {
+ select {
+ case <-t.C:
+ reconcileAll()
+ case <-ctx.Done():
+ return nil
+ }
+ }
+ }
+}
+
+func reconcile(ctx context.Context, r resource) error {
+ present, err := r.List(ctx)
+ if err != nil {
+ return err
+ }
+ presentSet := make(map[string]bool)
+ for _, el := range present {
+ presentSet[el] = true
+ }
+ expectedMap := r.Expected()
+ for name, el := range expectedMap {
+ if !presentSet[name] {
+ if err := r.Create(ctx, el); err != nil {
+ return err
+ }
+ }
+ }
+ for name, _ := range presentSet {
+ if _, ok := expectedMap[name]; !ok {
+ if err := r.Delete(ctx, name); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
diff --git a/metropolis/node/kubernetes/reconciler/reconciler_test.go b/metropolis/node/kubernetes/reconciler/reconciler_test.go
new file mode 100644
index 0000000..b58d4af
--- /dev/null
+++ b/metropolis/node/kubernetes/reconciler/reconciler_test.go
@@ -0,0 +1,184 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 reconciler
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ node "k8s.io/api/node/v1beta1"
+ policy "k8s.io/api/policy/v1beta1"
+ rbac "k8s.io/api/rbac/v1"
+ storage "k8s.io/api/storage/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// kubernetesMeta unwraps an interface{} that might contain a Kubernetes resource of type that is managed by the
+// reconciler. Any time a new Kubernetes type is managed by the reconciler, the following switch should be extended
+// to cover that type.
+func kubernetesMeta(v interface{}) *meta.ObjectMeta {
+ switch v2 := v.(type) {
+ case *rbac.ClusterRole:
+ return &v2.ObjectMeta
+ case *rbac.ClusterRoleBinding:
+ return &v2.ObjectMeta
+ case *storage.CSIDriver:
+ return &v2.ObjectMeta
+ case *storage.StorageClass:
+ return &v2.ObjectMeta
+ case *policy.PodSecurityPolicy:
+ return &v2.ObjectMeta
+ case *node.RuntimeClass:
+ return &v2.ObjectMeta
+ }
+ return nil
+}
+
+// TestExpectedNamedCorrectly ensures that all the Expected objects of all resource types have a correspondence between
+// their returned key and inner name. This contract must be met in order for the reconciler to not create runaway
+// resources. This assumes all managed resources are Kubernetes resources.
+func TestExpectedNamedCorrectly(t *testing.T) {
+ for reconciler, r := range allResources(nil) {
+ for outer, v := range r.Expected() {
+ meta := kubernetesMeta(v)
+ if meta == nil {
+ t.Errorf("reconciler %q, object %q: could not decode kubernetes metadata", reconciler, outer)
+ continue
+ }
+ if inner := meta.Name; outer != inner {
+ t.Errorf("reconciler %q, object %q: inner name mismatch (%q)", reconciler, outer, inner)
+ continue
+ }
+ }
+ }
+}
+
+// TestExpectedLabeledCorrectly ensures that all the Expected objects of all resource types have a Kubernetes metadata
+// label that signifies it's a builtin object, to be retrieved afterwards. This contract must be met in order for the
+// reconciler to not keep overwriting objects (and possibly failing), when a newly created object is not then
+// retrievable using a selector corresponding to this label. This assumes all managed resources are Kubernetes objects.
+func TestExpectedLabeledCorrectly(t *testing.T) {
+ for reconciler, r := range allResources(nil) {
+ for outer, v := range r.Expected() {
+ meta := kubernetesMeta(v)
+ if meta == nil {
+ t.Errorf("reconciler %q, object %q: could not decode kubernetes metadata", reconciler, outer)
+ continue
+ }
+ if data := meta.Labels[BuiltinLabelKey]; data != BuiltinLabelValue {
+ t.Errorf("reconciler %q, object %q: %q=%q, wanted =%q", reconciler, outer, BuiltinLabelKey, data, BuiltinLabelValue)
+ continue
+ }
+ }
+ }
+}
+
+// testResource is a resource type used for testing. The inner type is a string that is equal to its name (key).
+// It simulates a target (ie. k8s apiserver mock) that always acts nominally (all resources are created, deleted as
+// requested, and the state is consistent with requests).
+type testResource struct {
+ // current is the simulated state of resources in the target.
+ current map[string]string
+ // expected is what this type will report as the Expected() resources.
+ expected map[string]string
+}
+
+func (r *testResource) List(ctx context.Context) ([]string, error) {
+ var keys []string
+ for k, _ := range r.current {
+ keys = append(keys, k)
+ }
+ return keys, nil
+}
+
+func (r *testResource) Create(ctx context.Context, el interface{}) error {
+ r.current[el.(string)] = el.(string)
+ return nil
+}
+
+func (r *testResource) Delete(ctx context.Context, name string) error {
+ delete(r.current, name)
+ return nil
+}
+
+func (r *testResource) Expected() map[string]interface{} {
+ exp := make(map[string]interface{})
+ for k, v := range r.expected {
+ exp[k] = v
+ }
+ return exp
+}
+
+// newTestResource creates a test resource with a list of expected resource strings.
+func newTestResource(want ...string) *testResource {
+ expected := make(map[string]string)
+ for _, w := range want {
+ expected[w] = w
+ }
+ return &testResource{
+ current: make(map[string]string),
+ expected: expected,
+ }
+}
+
+// currentDiff returns a human-readable string showing the different between the current state and the given resource
+// strings. If no difference is present, the returned string is empty.
+func (r *testResource) currentDiff(want ...string) string {
+ expected := make(map[string]string)
+ for _, w := range want {
+ if _, ok := r.current[w]; !ok {
+ return fmt.Sprintf("%q missing in current", w)
+ }
+ expected[w] = w
+ }
+ for _, g := range r.current {
+ if _, ok := expected[g]; !ok {
+ return fmt.Sprintf("%q spurious in current", g)
+ }
+ }
+ return ""
+}
+
+// TestBasicReconciliation ensures that the reconcile function does manipulate a target state based on a set of
+// expected resources.
+func TestBasicReconciliation(t *testing.T) {
+ ctx := context.Background()
+ r := newTestResource("foo", "bar", "baz")
+
+ // nothing should have happened yet (testing the test)
+ if diff := r.currentDiff(); diff != "" {
+ t.Fatalf("wrong state after creation: %s", diff)
+ }
+
+ if err := reconcile(ctx, r); err != nil {
+ t.Fatalf("reconcile: %v", err)
+ }
+ // everything requested should have been created
+ if diff := r.currentDiff("foo", "bar", "baz"); diff != "" {
+ t.Fatalf("wrong state after reconciliation: %s", diff)
+ }
+
+ delete(r.expected, "foo")
+ if err := reconcile(ctx, r); err != nil {
+ t.Fatalf("reconcile: %v", err)
+ }
+ // foo should not be missing
+ if diff := r.currentDiff("bar", "baz"); diff != "" {
+ t.Fatalf("wrong state after deleting foo: %s", diff)
+ }
+}
diff --git a/metropolis/node/kubernetes/reconciler/resources_csi.go b/metropolis/node/kubernetes/reconciler/resources_csi.go
new file mode 100644
index 0000000..ecbcb4b
--- /dev/null
+++ b/metropolis/node/kubernetes/reconciler/resources_csi.go
@@ -0,0 +1,71 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 reconciler
+
+import (
+ "context"
+
+ storage "k8s.io/api/storage/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+// TODO(q3k): this is duplicated with //metropolis/node/kubernetes:provisioner.go; integrate this once provisioner.go
+// gets moved into a subpackage.
+// ONCHANGE(//metropolis/node/kubernetes:provisioner.go): needs to match csiProvisionerName declared.
+const csiProvisionerName = "com.nexantic.smalltown.vfs"
+
+type resourceCSIDrivers struct {
+ kubernetes.Interface
+}
+
+func (r resourceCSIDrivers) List(ctx context.Context) ([]string, error) {
+ res, err := r.StorageV1().CSIDrivers().List(ctx, listBuiltins)
+ if err != nil {
+ return nil, err
+ }
+ objs := make([]string, len(res.Items))
+ for i, el := range res.Items {
+ objs[i] = el.ObjectMeta.Name
+ }
+ return objs, nil
+}
+
+func (r resourceCSIDrivers) Create(ctx context.Context, el interface{}) error {
+ _, err := r.StorageV1().CSIDrivers().Create(ctx, el.(*storage.CSIDriver), meta.CreateOptions{})
+ return err
+}
+
+func (r resourceCSIDrivers) Delete(ctx context.Context, name string) error {
+ return r.StorageV1().CSIDrivers().Delete(ctx, name, meta.DeleteOptions{})
+}
+
+func (r resourceCSIDrivers) Expected() map[string]interface{} {
+ return map[string]interface{}{
+ csiProvisionerName: &storage.CSIDriver{
+ ObjectMeta: meta.ObjectMeta{
+ Name: csiProvisionerName,
+ Labels: builtinLabels(nil),
+ },
+ Spec: storage.CSIDriverSpec{
+ AttachRequired: False(),
+ PodInfoOnMount: False(),
+ VolumeLifecycleModes: []storage.VolumeLifecycleMode{storage.VolumeLifecyclePersistent},
+ },
+ },
+ }
+}
diff --git a/metropolis/node/kubernetes/reconciler/resources_podsecuritypolicy.go b/metropolis/node/kubernetes/reconciler/resources_podsecuritypolicy.go
new file mode 100644
index 0000000..507089f
--- /dev/null
+++ b/metropolis/node/kubernetes/reconciler/resources_podsecuritypolicy.go
@@ -0,0 +1,108 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 reconciler
+
+import (
+ "context"
+
+ core "k8s.io/api/core/v1"
+ policy "k8s.io/api/policy/v1beta1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+type resourcePodSecurityPolicies struct {
+ kubernetes.Interface
+}
+
+func (r resourcePodSecurityPolicies) List(ctx context.Context) ([]string, error) {
+ res, err := r.PolicyV1beta1().PodSecurityPolicies().List(ctx, listBuiltins)
+ if err != nil {
+ return nil, err
+ }
+ objs := make([]string, len(res.Items))
+ for i, el := range res.Items {
+ objs[i] = el.ObjectMeta.Name
+ }
+ return objs, nil
+}
+
+func (r resourcePodSecurityPolicies) Create(ctx context.Context, el interface{}) error {
+ _, err := r.PolicyV1beta1().PodSecurityPolicies().Create(ctx, el.(*policy.PodSecurityPolicy), meta.CreateOptions{})
+ return err
+}
+
+func (r resourcePodSecurityPolicies) Delete(ctx context.Context, name string) error {
+ return r.PolicyV1beta1().PodSecurityPolicies().Delete(ctx, name, meta.DeleteOptions{})
+}
+
+func (r resourcePodSecurityPolicies) Expected() map[string]interface{} {
+ return map[string]interface{}{
+ "default": &policy.PodSecurityPolicy{
+ ObjectMeta: meta.ObjectMeta{
+ Name: "default",
+ Labels: builtinLabels(nil),
+ Annotations: map[string]string{
+ "kubernetes.io/description": "This default PSP allows the creation of pods using features that are" +
+ " generally considered safe against any sort of escape.",
+ },
+ },
+ Spec: policy.PodSecurityPolicySpec{
+ AllowPrivilegeEscalation: True(),
+ AllowedCapabilities: []core.Capability{ // runc's default list of allowed capabilities
+ "SETPCAP",
+ "MKNOD",
+ "AUDIT_WRITE",
+ "CHOWN",
+ "NET_RAW",
+ "DAC_OVERRIDE",
+ "FOWNER",
+ "FSETID",
+ "KILL",
+ "SETGID",
+ "SETUID",
+ "NET_BIND_SERVICE",
+ "SYS_CHROOT",
+ "SETFCAP",
+ },
+ HostNetwork: false,
+ HostIPC: false,
+ HostPID: false,
+ FSGroup: policy.FSGroupStrategyOptions{
+ Rule: policy.FSGroupStrategyRunAsAny,
+ },
+ RunAsUser: policy.RunAsUserStrategyOptions{
+ Rule: policy.RunAsUserStrategyRunAsAny,
+ },
+ SELinux: policy.SELinuxStrategyOptions{
+ Rule: policy.SELinuxStrategyRunAsAny,
+ },
+ SupplementalGroups: policy.SupplementalGroupsStrategyOptions{
+ Rule: policy.SupplementalGroupsStrategyRunAsAny,
+ },
+ Volumes: []policy.FSType{ // Volumes considered safe to use
+ policy.ConfigMap,
+ policy.EmptyDir,
+ policy.Projected,
+ policy.Secret,
+ policy.DownwardAPI,
+ policy.PersistentVolumeClaim,
+ },
+ },
+ },
+ }
+}
diff --git a/metropolis/node/kubernetes/reconciler/resources_rbac.go b/metropolis/node/kubernetes/reconciler/resources_rbac.go
new file mode 100644
index 0000000..40ca879
--- /dev/null
+++ b/metropolis/node/kubernetes/reconciler/resources_rbac.go
@@ -0,0 +1,154 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 reconciler
+
+import (
+ "context"
+
+ rbac "k8s.io/api/rbac/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+var (
+ clusterRolePSPDefault = builtinRBACName("psp-default")
+ clusterRoleBindingDefaultPSP = builtinRBACName("default-psp-for-sa")
+ clusterRoleBindingAPIServerKubeletClient = builtinRBACName("apiserver-kubelet-client")
+)
+
+type resourceClusterRoles struct {
+ kubernetes.Interface
+}
+
+func (r resourceClusterRoles) List(ctx context.Context) ([]string, error) {
+ res, err := r.RbacV1().ClusterRoles().List(ctx, listBuiltins)
+ if err != nil {
+ return nil, err
+ }
+ objs := make([]string, len(res.Items))
+ for i, el := range res.Items {
+ objs[i] = el.ObjectMeta.Name
+ }
+ return objs, nil
+}
+
+func (r resourceClusterRoles) Create(ctx context.Context, el interface{}) error {
+ _, err := r.RbacV1().ClusterRoles().Create(ctx, el.(*rbac.ClusterRole), meta.CreateOptions{})
+ return err
+}
+
+func (r resourceClusterRoles) Delete(ctx context.Context, name string) error {
+ return r.RbacV1().ClusterRoles().Delete(ctx, name, meta.DeleteOptions{})
+}
+
+func (r resourceClusterRoles) Expected() map[string]interface{} {
+ return map[string]interface{}{
+ clusterRolePSPDefault: &rbac.ClusterRole{
+ ObjectMeta: meta.ObjectMeta{
+ Name: clusterRolePSPDefault,
+ Labels: builtinLabels(nil),
+ Annotations: map[string]string{
+ "kubernetes.io/description": "This role grants access to the \"default\" PSP.",
+ },
+ },
+ Rules: []rbac.PolicyRule{
+ {
+ APIGroups: []string{"policy"},
+ Resources: []string{"podsecuritypolicies"},
+ ResourceNames: []string{"default"},
+ Verbs: []string{"use"},
+ },
+ },
+ },
+ }
+}
+
+type resourceClusterRoleBindings struct {
+ kubernetes.Interface
+}
+
+func (r resourceClusterRoleBindings) List(ctx context.Context) ([]string, error) {
+ res, err := r.RbacV1().ClusterRoleBindings().List(ctx, listBuiltins)
+ if err != nil {
+ return nil, err
+ }
+ objs := make([]string, len(res.Items))
+ for i, el := range res.Items {
+ objs[i] = el.ObjectMeta.Name
+ }
+ return objs, nil
+}
+
+func (r resourceClusterRoleBindings) Create(ctx context.Context, el interface{}) error {
+ _, err := r.RbacV1().ClusterRoleBindings().Create(ctx, el.(*rbac.ClusterRoleBinding), meta.CreateOptions{})
+ return err
+}
+
+func (r resourceClusterRoleBindings) Delete(ctx context.Context, name string) error {
+ return r.RbacV1().ClusterRoleBindings().Delete(ctx, name, meta.DeleteOptions{})
+}
+
+func (r resourceClusterRoleBindings) Expected() map[string]interface{} {
+ return map[string]interface{}{
+ clusterRoleBindingDefaultPSP: &rbac.ClusterRoleBinding{
+ ObjectMeta: meta.ObjectMeta{
+ Name: clusterRoleBindingDefaultPSP,
+ Labels: builtinLabels(nil),
+ Annotations: map[string]string{
+ "kubernetes.io/description": "This binding grants every service account access to the \"default\" PSP. " +
+ "Creation of Pods is still restricted by other RBAC roles. Otherwise no pods (unprivileged or not) " +
+ "can be created.",
+ },
+ },
+ RoleRef: rbac.RoleRef{
+ APIGroup: rbac.GroupName,
+ Kind: "ClusterRole",
+ Name: clusterRolePSPDefault,
+ },
+ Subjects: []rbac.Subject{
+ {
+ APIGroup: rbac.GroupName,
+ Kind: "Group",
+ Name: "system:serviceaccounts",
+ },
+ },
+ },
+ clusterRoleBindingAPIServerKubeletClient: &rbac.ClusterRoleBinding{
+ ObjectMeta: meta.ObjectMeta{
+ Name: clusterRoleBindingAPIServerKubeletClient,
+ Labels: builtinLabels(nil),
+ Annotations: map[string]string{
+ "kubernetes.io/description": "This binding grants the apiserver access to the kubelets. This enables " +
+ "lots of built-in functionality like reading logs or forwarding ports via the API.",
+ },
+ },
+ RoleRef: rbac.RoleRef{
+ APIGroup: rbac.GroupName,
+ Kind: "ClusterRole",
+ Name: "system:kubelet-api-admin",
+ },
+ Subjects: []rbac.Subject{
+ {
+ APIGroup: rbac.GroupName,
+ Kind: "User",
+ // TODO(q3k): describe this name's contract, or unify with whatever creates this.
+ Name: "smalltown:apiserver-kubelet-client",
+ },
+ },
+ },
+ }
+}
diff --git a/metropolis/node/kubernetes/reconciler/resources_runtimeclass.go b/metropolis/node/kubernetes/reconciler/resources_runtimeclass.go
new file mode 100644
index 0000000..c202c0e
--- /dev/null
+++ b/metropolis/node/kubernetes/reconciler/resources_runtimeclass.go
@@ -0,0 +1,69 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 reconciler
+
+import (
+ "context"
+
+ node "k8s.io/api/node/v1beta1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+type resourceRuntimeClasses struct {
+ kubernetes.Interface
+}
+
+func (r resourceRuntimeClasses) List(ctx context.Context) ([]string, error) {
+ res, err := r.NodeV1beta1().RuntimeClasses().List(ctx, listBuiltins)
+ if err != nil {
+ return nil, err
+ }
+ objs := make([]string, len(res.Items))
+ for i, el := range res.Items {
+ objs[i] = el.ObjectMeta.Name
+ }
+ return objs, nil
+}
+
+func (r resourceRuntimeClasses) Create(ctx context.Context, el interface{}) error {
+ _, err := r.NodeV1beta1().RuntimeClasses().Create(ctx, el.(*node.RuntimeClass), meta.CreateOptions{})
+ return err
+}
+
+func (r resourceRuntimeClasses) Delete(ctx context.Context, name string) error {
+ return r.NodeV1beta1().RuntimeClasses().Delete(ctx, name, meta.DeleteOptions{})
+}
+
+func (r resourceRuntimeClasses) Expected() map[string]interface{} {
+ return map[string]interface{}{
+ "gvisor": &node.RuntimeClass{
+ ObjectMeta: meta.ObjectMeta{
+ Name: "gvisor",
+ Labels: builtinLabels(nil),
+ },
+ Handler: "runsc",
+ },
+ "runc": &node.RuntimeClass{
+ ObjectMeta: meta.ObjectMeta{
+ Name: "runc",
+ Labels: builtinLabels(nil),
+ },
+ Handler: "runc",
+ },
+ }
+}
diff --git a/metropolis/node/kubernetes/reconciler/resources_storageclass.go b/metropolis/node/kubernetes/reconciler/resources_storageclass.go
new file mode 100644
index 0000000..72476ec
--- /dev/null
+++ b/metropolis/node/kubernetes/reconciler/resources_storageclass.go
@@ -0,0 +1,72 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 reconciler
+
+import (
+ "context"
+
+ core "k8s.io/api/core/v1"
+ storage "k8s.io/api/storage/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+var reclaimPolicyDelete = core.PersistentVolumeReclaimDelete
+var waitForConsumerBinding = storage.VolumeBindingWaitForFirstConsumer
+
+type resourceStorageClasses struct {
+ kubernetes.Interface
+}
+
+func (r resourceStorageClasses) List(ctx context.Context) ([]string, error) {
+ res, err := r.StorageV1().StorageClasses().List(ctx, listBuiltins)
+ if err != nil {
+ return nil, err
+ }
+ objs := make([]string, len(res.Items))
+ for i, el := range res.Items {
+ objs[i] = el.ObjectMeta.Name
+ }
+ return objs, nil
+}
+
+func (r resourceStorageClasses) Create(ctx context.Context, el interface{}) error {
+ _, err := r.StorageV1().StorageClasses().Create(ctx, el.(*storage.StorageClass), meta.CreateOptions{})
+ return err
+}
+
+func (r resourceStorageClasses) Delete(ctx context.Context, name string) error {
+ return r.StorageV1().StorageClasses().Delete(ctx, name, meta.DeleteOptions{})
+}
+
+func (r resourceStorageClasses) Expected() map[string]interface{} {
+ return map[string]interface{}{
+ "local": &storage.StorageClass{
+ ObjectMeta: meta.ObjectMeta{
+ Name: "local",
+ Labels: builtinLabels(nil),
+ Annotations: map[string]string{
+ "storageclass.kubernetes.io/is-default-class": "true",
+ },
+ },
+ AllowVolumeExpansion: True(),
+ Provisioner: csiProvisionerName,
+ ReclaimPolicy: &reclaimPolicyDelete,
+ VolumeBindingMode: &waitForConsumerBinding,
+ },
+ }
+}