Created
December 20, 2019 04:37
-
-
Save displague/5b955fb471b6fcd9a2918212594d82df to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/design/one-pager-stacks-security-isolation.md b/design/one-pager-stacks-security-isolation.md | |
index 5700a46d..effe0164 100644 | |
--- a/design/one-pager-stacks-security-isolation.md | |
+++ b/design/one-pager-stacks-security-isolation.md | |
@@ -342,15 +342,15 @@ The following built in roles will be installed and packaged with Crossplane, and | |
The verbs supported in these roles(admin,edit,view) will model behavior based on existing logic around | |
[built-in kubernetes roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#default-roles-and-role-bindings). | |
-|ClusterRole Name | Permissions | | |
-|:---------------------|:----------| | |
-| crossplane-admin | Admin: ClusterStacks, Crossplane CRDs, Namespaces, (Cluster)RoleBindings, (Cluster)Roles | | |
-| crossplane-env-admin | Admin: ClusterStacks, CustomResource types installed by Cluster Scoped Stacks | | |
-| crossplane-env-edit | Edit: ClusterStacks, CustomResource types installed by Cluster Scoped Stacks | | |
-| crossplane-env-view | View: CustomResource types installed by Cluster Scoped Stacks, Not permitted: secrets | | |
+|ClusterRole Name | Kubernetes Counterpart | Permissions | | |
+|:---------------------|:----------|:----------| | |
+| crossplane-admin | cluster-admin | Admin: ClusterStacks, Crossplane CRDs, Namespaces, (Cluster)RoleBindings, (Cluster)Roles | | |
+| crossplane-env-admin | admin | Admin: ClusterStacks, CustomResource types installed by Cluster Scoped Stacks | | |
+| crossplane-env-edit | edit | Edit: ClusterStacks, CustomResource types installed by Cluster Scoped Stacks | | |
+| crossplane-env-view | view | View: CustomResource types installed by Cluster Scoped Stacks, Not permitted: secrets | | |
We will provide a convenience group - `crossplane:masters` - that binds to `crossplane-admin` with a | |
- ClusterRoleBinding. This mirrors Kubernetes functionality for cluster-admin and system:master. At install time you will | |
+ ClusterRoleBinding. This mirrors Kubernetes functionality for `cluster-admin` and `system:masters`. At install time you will | |
then be able to impersonate a group that is bound to crossplane-admin. | |
Example running a command as crossplane-admin | |
@@ -369,10 +369,11 @@ Match labels on these roles for auto-aggregation: | |
Each namespace provides a unit of isolation with namespaced stacks, and thus a unique set of resources, based on the | |
stacks that have been installed into that namespace. If someone were allowed to install stacks, it can't be assumed | |
that they are cluster-admin, so the stack-manager will install and manage a set of roles that will aggregate the roles | |
- for the installed stacks into namespace specific roles for self-service. These roles should be bound to subjects | |
- to grant access to a particular namespace for end-user integration. For any namespace that is annotated with | |
- `rbac.crossplane.io/managed-roles: true` the stack-manager will ensure that ClusterRoles in the following format are | |
- created for the annotated namespace. | |
+ for the installed stacks into namespace specific roles for self-service. These `ClusterRoles` should be bound to subjects | |
+ using a `RoleBinding` (not `ClusterRoleBinding`) to grant access to a particular namespace for end-user integration. | |
+ | |
+This design calls for the stack-manager to act on any namespace that is annotated with `rbac.crossplane.io/managed-roles: true`, | |
+ensuring that ClusterRoles in the following format are created for the annotated namespace. | |
* `crossplane:ns:{ns-name}:admin` | |
* `crossplane:ns:{ns-name}:edit` | |
@@ -386,6 +387,13 @@ Match Roles: | |
* `rbac.crossplane.io/aggregate-to-namespace-{role}: "true"` | |
* `namespace.crossplane.io/{namespace-name}: true // to match specific namespace.` | |
+|ClusterRole Name | Kubernetes Counterpart | Permissions | | |
+|:---------------------|:----------|:----------| | |
+| crossplane:ns:{ns-name}:admin | admin | Admin: StackInstalls, Stacks, ConfigMaps, Secrets, CustomResource types installed by Namespace Scoped Stacks | | |
+| crossplane:ns:{ns-name}:edit | edit | Edit: StackInstalls, ConfigMaps, Secrets, CustomResource types installed by Namespace Scoped Stacks | | |
+| crossplane:ns:{ns-name}:view | view | View: StackInstalls, CustomResource types installed by Namespace Scoped Stacks, Not permitted: secrets | | |
+ | |
+ | |
#### ClusterRoles for a given (Cluster)Stack Install | |
For a given stack version the stack-manager will create a unique set of roles in the format | |
@@ -394,14 +402,16 @@ For a given stack version the stack-manager will create a unique set of roles in | |
stack. The other (admin,edit,view) will be labeled for aggregation to their appropriate namespaced or environment | |
scoped crossplane managed roles. | |
-Consider the following example for a wordpress stack of version 1.1 from wordpressinc, you would have the following set. | |
- * `stack:wordpressinc:wordpress:1.1:system` // Used by wordpress operator - stack resources + dependent resources grants | |
+Consider the following example for a wordpress stack of version 1.1 from wordpressinc (installed in a "wordpressinc" namespace), | |
+you would have the following set. | |
+ | |
+* `stack:wordpressinc:wordpress:1.1:system` // Used by wordpress operator - stack resources + dependent resources grants | |
* `stack:wordpressinc:wordpress:1.1:admin` | |
* `stack:wordpressinc:wordpress:1.1:edit` | |
* `stack:wordpressinc:wordpress:1.1:view` | |
If these were a from a cluster scoped stack, they would be aggregated to the built in environment roles, and if they | |
- were from a namespaced stack install they would be aggregated to the corresponding [ClusterRoles for a given app | |
+ were from a namespaced `StackInstall` they would be aggregated to the corresponding [ClusterRoles for a given app | |
namespace](#clusterroles-for-a-given-app-namespace). | |
Labels for these roles will be for aggregation purposes: | |
@@ -421,6 +431,9 @@ We've defined the roles that get created that will aggregate the permissions of | |
bound to RBAC subjects, like a group or User, but these roles themselves need to be seeded with initial permissions | |
so that a user who has a namespace or environment level role could for example install the first stack. | |
+These roles share a common function to the Kubernetes `system:aggregate-to-view`, `system:aggregate-to-edit`, | |
+and `system:aggregate-to-admin` clusterroles. | |
+ | |
Defaults that stack-manager installs that would aggregate to the crossplane-env-{role} `ClusterRole` objects, | |
for example, permission on `ClusterStackInstall` objects. | |
@@ -495,7 +508,7 @@ metadata: | |
rules: | |
- apiGroups: ["providers.aws.crossplane.io"] | |
resources: ["Provider"] | |
- verbs: ["get", "list", "watch", "edit", "delete"] | |
+ verbs: ["get", "list", "watch", "create", "update", "patch", "deletecollection", "delete"] | |
... | |
``` | |
@@ -513,7 +526,7 @@ metadata: | |
rules: | |
- apiGroups: ["wordpress.crossplane.io"] | |
resources: ["WordpressInstance"] | |
- verbs: ["get", "list", "watch", "edit", "delete"] | |
+ verbs: ["get", "list", "watch"] | |
... | |
``` | |
diff --git a/docs/install-crossplane.md b/docs/install-crossplane.md | |
index ae6d471f..b69188c9 100644 | |
--- a/docs/install-crossplane.md | |
+++ b/docs/install-crossplane.md | |
@@ -240,6 +240,7 @@ The following tables lists the configurable parameters of the Crossplane chart a | |
| `clusterStacks.azure.version` | Azure stack version to deploy | `<latest released version>` | |
| `clusterStacks.rook.deploy` | Deploy Rook stack | `false` | |
| `clusterStacks.rook.version` | Rook stack version to deploy | `<latest released version>` | |
+| `personas.deploy` | Install roles and bindings for Crossplane user personas | `true` | |
### Command Line | |
diff --git a/pkg/controller/stacks/install/installjob.go b/pkg/controller/stacks/install/installjob.go | |
index 7af3e7a2..c82a54b5 100644 | |
--- a/pkg/controller/stacks/install/installjob.go | |
+++ b/pkg/controller/stacks/install/installjob.go | |
@@ -46,7 +46,6 @@ var ( | |
jobBackoff = int32(0) | |
registryDirName = ".registry" | |
packageContentsVolumeName = "package-contents" | |
- labelNamespaceFmt = "namespace.crossplane.io/%s" | |
) | |
// JobCompleter is an interface for handling job completion | |
@@ -251,8 +250,13 @@ func (jc *stackInstallJobCompleter) createJobOutputObject(ctx context.Context, o | |
// instead. | |
labels := stacks.ParentLabels(i) | |
+ // CRDs are labeled with the namespaces of the stacks they are managed by. | |
+ // This will allow for a single Namespaced stack to be installed in multiple | |
+ // namespaces, or different stacks (possibly only differing by versions) to | |
+ // provide the same CRDs without the risk that a single StackInstall removal | |
+ // will delete a CRD until there are no remaining namespace labels. | |
if isCRDObject(obj) { | |
- labelNamespace := fmt.Sprintf(labelNamespaceFmt, i.GetNamespace()) | |
+ labelNamespace := fmt.Sprintf(stacks.LabelNamespaceFmt, i.GetNamespace()) | |
labels[labelNamespace] = "true" | |
} | |
diff --git a/pkg/controller/stacks/install/installjob_test.go b/pkg/controller/stacks/install/installjob_test.go | |
index e2cb4e16..96992de6 100644 | |
--- a/pkg/controller/stacks/install/installjob_test.go | |
+++ b/pkg/controller/stacks/install/installjob_test.go | |
@@ -562,13 +562,13 @@ func TestCreate(t *testing.T) { | |
func TestCreateJobOutputObject(t *testing.T) { | |
wantLabels := map[string]string{ | |
- stacks.LabelParentGroup: "stacks.crossplane.io", | |
- stacks.LabelParentVersion: "v1alpha1", | |
- stacks.LabelParentKind: "StackInstall", | |
- stacks.LabelParentNamespace: namespace, | |
- stacks.LabelParentName: resourceName, | |
- stacks.LabelParentUID: uidString, | |
- fmt.Sprintf(labelNamespaceFmt, namespace): "true", | |
+ stacks.LabelParentGroup: "stacks.crossplane.io", | |
+ stacks.LabelParentVersion: "v1alpha1", | |
+ stacks.LabelParentKind: "StackInstall", | |
+ stacks.LabelParentNamespace: namespace, | |
+ stacks.LabelParentName: resourceName, | |
+ stacks.LabelParentUID: uidString, | |
+ fmt.Sprintf(stacks.LabelNamespaceFmt, namespace): "true", | |
} | |
type want struct { | |
diff --git a/pkg/controller/stacks/install/stackinstall.go b/pkg/controller/stacks/install/stackinstall.go | |
index 8c11237c..6d266618 100644 | |
--- a/pkg/controller/stacks/install/stackinstall.go | |
+++ b/pkg/controller/stacks/install/stackinstall.go | |
@@ -255,27 +255,31 @@ func (h *stackInstallHandler) update(ctx context.Context) (reconcile.Result, err | |
// This function ensures that all the resources (e.g., CRDs) that this StackInstall owns | |
// are also cleaned up. | |
func (h *stackInstallHandler) delete(ctx context.Context) (reconcile.Result, error) { | |
+ // Delete all Stacks created by this StackInstall or ClusterStackInstall | |
labels := stacks.ParentLabels(h.ext) | |
if err := h.kube.DeleteAllOf(ctx, &v1alpha1.Stack{}, client.InNamespace(h.ext.GetNamespace()), client.MatchingLabels(labels)); runtimeresource.IgnoreNotFound(err) != nil { | |
return fail(ctx, h.kube, h.ext, err) | |
} | |
+ // Waiting for all Stacks to clear their finalizers and delete before | |
+ // deleting the CRDs that they depend on | |
stackList := &v1alpha1.StackList{} | |
if err := h.kube.List(ctx, stackList, client.MatchingLabels(labels)); err != nil { | |
return fail(ctx, h.kube, h.ext, err) | |
} | |
- // Waiting for all Stacks to be clear their finalizers and delete before | |
- // deleting the CRDs that they depend on | |
if len(stackList.Items) != 0 { | |
err := errors.New("Stack resources have not been deleted") | |
return fail(ctx, h.kube, h.ext, err) | |
} | |
+ // Once the Stacks are gone, we can remove all of the CRDs associated | |
+ // with the StackInstall | |
if err := h.kube.DeleteAllOf(ctx, &apiextensionsv1beta1.CustomResourceDefinition{}, client.MatchingLabels(labels)); runtimeresource.IgnoreNotFound(err) != nil { | |
return fail(ctx, h.kube, h.ext, err) | |
} | |
+ // And finally clear the StackInstall's own finalizer | |
meta.RemoveFinalizer(h.ext, installFinalizer) | |
if err := h.kube.Update(ctx, h.ext); err != nil { | |
return fail(ctx, h.kube, h.ext, err) | |
diff --git a/pkg/controller/stacks/stack/stack.go b/pkg/controller/stacks/stack/stack.go | |
index b8866723..a1945bd7 100644 | |
--- a/pkg/controller/stacks/stack/stack.go | |
+++ b/pkg/controller/stacks/stack/stack.go | |
@@ -44,12 +44,8 @@ import ( | |
) | |
const ( | |
- controllerName = "stack.stacks.crossplane.io" | |
- clusterRoleNameFmt = "stack:%s:%s:%s:%s" | |
- labelScope = "crossplane.io/scope" | |
- labelNamespaceFmt = "namespace.crossplane.io/%s" | |
- labelAggregateFmt = "rbac.crossplane.io/aggregate-to-%s-%s" | |
- stacksFinalizer = "finalizer.stacks.crossplane.io" | |
+ controllerName = "stack.stacks.crossplane.io" | |
+ stacksFinalizer = "finalizer.stacks.crossplane.io" | |
reconcileTimeout = 1 * time.Minute | |
requeueAfterOnSuccess = 10 * time.Second | |
@@ -61,8 +57,8 @@ var ( | |
requeueOnSuccess = reconcile.Result{RequeueAfter: requeueAfterOnSuccess} | |
roleVerbs = map[string][]string{ | |
- "admin": {"get", "list", "watch", "edit", "create", "delete"}, | |
- "edit": {"get", "list", "watch", "edit"}, | |
+ "admin": {"get", "list", "watch", "create", "delete", "deletecollection", "patch", "update"}, | |
+ "edit": {"get", "list", "watch", "create", "delete", "deletecollection", "patch", "update"}, | |
"view": {"get", "list", "watch"}, | |
} | |
) | |
@@ -191,13 +187,34 @@ func (h *stackHandler) update(ctx context.Context) (reconcile.Result, error) { | |
// createPersonaClusterRoles creates admin, edit, and view clusterroles that are | |
// namespace+stack+version specific | |
func (h *stackHandler) createPersonaClusterRoles(ctx context.Context, labels map[string]string) error { | |
- // TODO(displague) refine 'editor' persona roles | |
for persona := range roleVerbs { | |
+ name := stacks.PersonaRoleName(h.ext, persona) | |
+ | |
+ // Use a copy so AddLabels doesn't mutate labels | |
labelsCopy := map[string]string{} | |
for k, v := range labels { | |
labelsCopy[k] = v | |
} | |
+ // Create labels appropriate for the scope of the ClusterRole | |
+ var crossplaneScope string | |
+ | |
+ if h.isNamespaced() { | |
+ crossplaneScope = stacks.NamespaceScoped | |
+ | |
+ labelNamespace := fmt.Sprintf(stacks.LabelNamespaceFmt, h.ext.GetNamespace()) | |
+ labelsCopy[labelNamespace] = "true" | |
+ } else { | |
+ crossplaneScope = stacks.EnvironmentScoped | |
+ } | |
+ | |
+ // Aggregation labels grant Stack CRD responsibilities | |
+ // to namespace or environment personas like crossplane-env-admin | |
+ // or crossplane:ns:default:view | |
+ aggregationLabel := fmt.Sprintf(stacks.LabelAggregateFmt, crossplaneScope, persona) | |
+ labelsCopy[aggregationLabel] = "true" | |
+ | |
+ // Each ClusterRole needs persona specific rules for each CRD | |
rules := []rbacv1.PolicyRule{} | |
for _, crd := range h.ext.Spec.CRDs { | |
rules = append(rules, rbacv1.PolicyRule{ | |
@@ -206,18 +223,8 @@ func (h *stackHandler) createPersonaClusterRoles(ctx context.Context, labels map | |
Verbs: roleVerbs[persona], | |
}) | |
} | |
- name := fmtPersonaRole(h.ext, persona) | |
- | |
- var crossplaneScope string | |
- | |
- if h.isNamespaced() { | |
- crossplaneScope = "namespace" | |
- } else { | |
- crossplaneScope = "environment" | |
- } | |
- | |
- aggregationLabel := fmt.Sprintf(labelAggregateFmt, crossplaneScope, persona) | |
+ // Assemble and create the ClusterRole | |
cr := &rbacv1.ClusterRole{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: name, | |
@@ -226,18 +233,6 @@ func (h *stackHandler) createPersonaClusterRoles(ctx context.Context, labels map | |
Rules: rules, | |
} | |
- meta.AddLabels(cr, map[string]string{ | |
- aggregationLabel: "true", | |
- }) | |
- | |
- meta.AddLabels(cr, stacks.ParentLabels(h.ext)) | |
- | |
- if h.isNamespaced() { | |
- meta.AddLabels(cr, map[string]string{ | |
- fmt.Sprintf(labelNamespaceFmt, h.ext.GetNamespace()): "true", | |
- }) | |
- } | |
- | |
if err := h.kube.Create(ctx, cr); err != nil && !kerrors.IsAlreadyExists(err) { | |
return errors.Wrap(err, "failed to create persona cluster roles") | |
} | |
@@ -245,40 +240,40 @@ func (h *stackHandler) createPersonaClusterRoles(ctx context.Context, labels map | |
return nil | |
} | |
+// generateNamespaceClusterRoles generates the crossplane:ns:{name}:{persona} | |
+// roles for a given stack's namespace. | |
func generateNamespaceClusterRoles(stack *v1alpha1.Stack) (roles []*rbacv1.ClusterRole) { | |
personas := []string{"admin", "edit", "view"} | |
- namespaced := (stack.Spec.PermissionScope == string(apiextensions.NamespaceScoped)) | |
- if !namespaced { | |
- return | |
- } | |
- | |
nsName := stack.GetNamespace() | |
for _, persona := range personas { | |
- name := fmt.Sprintf("crossplane:ns:%s:%s", nsName, persona) | |
+ name := fmt.Sprintf(stacks.NamespaceClusterRoleNameFmt, nsName, persona) | |
labels := map[string]string{ | |
- fmt.Sprintf(labelNamespaceFmt, nsName): "true", | |
- labelScope: "namespace", | |
+ fmt.Sprintf(stacks.LabelNamespaceFmt, nsName): "true", | |
+ stacks.LabelScope: stacks.NamespaceScoped, | |
} | |
if persona == "admin" { | |
- labels[fmt.Sprintf(labelAggregateFmt, "crossplane", persona)] = "true" | |
+ labels[fmt.Sprintf(stacks.LabelAggregateFmt, "crossplane", persona)] = "true" | |
} | |
+ // By specifying MatchLabels, ClusterRole Aggregation will pass | |
+ // along the rules from other ClusterRoles with the desired labels. | |
+ // This is why we don't define any Rules here. | |
role := &rbacv1.ClusterRole{ | |
AggregationRule: &rbacv1.AggregationRule{ | |
ClusterRoleSelectors: []metav1.LabelSelector{ | |
{ | |
MatchLabels: map[string]string{ | |
- fmt.Sprintf(labelAggregateFmt, "namespace", persona): "true", | |
- fmt.Sprintf(labelNamespaceFmt, nsName): "true", | |
+ fmt.Sprintf(stacks.LabelAggregateFmt, stacks.NamespaceScoped, persona): "true", | |
+ fmt.Sprintf(stacks.LabelNamespaceFmt, nsName): "true", | |
}, | |
}, | |
{ | |
MatchLabels: map[string]string{ | |
- fmt.Sprintf(labelAggregateFmt, "namespace-default", persona): "true", | |
+ fmt.Sprintf(stacks.LabelAggregateFmt, "namespace-default", persona): "true", | |
}, | |
}, | |
}, | |
@@ -297,16 +292,28 @@ func generateNamespaceClusterRoles(stack *v1alpha1.Stack) (roles []*rbacv1.Clust | |
return roles | |
} | |
+// createNamespaceClusterRoles creates the crossplane:ns:{name}:{persona} | |
+// roles for a given stack's namespace | |
func (h *stackHandler) createNamespaceClusterRoles(ctx context.Context) error { | |
+ if !h.isNamespaced() { | |
+ return nil | |
+ } | |
+ | |
+ // Get the Namepsace because we need the UID for OwnerReference | |
ns := &corev1.Namespace{} | |
nsName := h.ext.GetNamespace() | |
if err := h.kube.Get(ctx, types.NamespacedName{Name: nsName}, ns); err != nil { | |
- return errors.Wrapf(err, "failed to get namespace %q for stackinstall %q", nsName, h.ext.GetName()) | |
+ return errors.Wrapf(err, "failed to get namespace %q for stack %q", nsName, h.ext.GetName()) | |
} | |
+ | |
+ // generate namespace + stack specific clusterroles for all personas | |
roles := generateNamespaceClusterRoles(h.ext) | |
for _, role := range roles { | |
+ // When the namespace is deleted, we no longer need these clusterroles. | |
+ // Set the ClusterRole owner to the Namespace so they are cleaned up | |
+ // automatically | |
role.SetOwnerReferences([]metav1.OwnerReference{ | |
{ | |
APIVersion: "v1", | |
@@ -316,15 +323,18 @@ func (h *stackHandler) createNamespaceClusterRoles(ctx context.Context) error { | |
}, | |
}) | |
+ // Creating the clusterroles. Since these Rules in these clusterroles | |
+ // are populated through aggregation from the stacks installed in the | |
+ // namespaces, we won't need to update them. | |
if err := h.kube.Create(ctx, role); err != nil && !kerrors.IsAlreadyExists(err) { | |
- return errors.Wrapf(err, "failed to create clusterrole %s for stackinstall %s", role.GetName(), h.ext.GetName()) | |
+ return errors.Wrapf(err, "failed to create clusterrole %s for stack %s", role.GetName(), h.ext.GetName()) | |
} | |
} | |
return nil | |
} | |
func (h *stackHandler) createDeploymentClusterRole(ctx context.Context, labels map[string]string) (string, error) { | |
- name := fmtPersonaRole(h.ext, "system") | |
+ name := stacks.PersonaRoleName(h.ext, "system") | |
cr := &rbacv1.ClusterRole{ | |
ObjectMeta: metav1.ObjectMeta{ | |
Name: name, | |
@@ -538,9 +548,3 @@ func fail(ctx context.Context, kube client.StatusClient, i *v1alpha1.Stack, err | |
i.Status.SetConditions(runtimev1alpha1.ReconcileError(err)) | |
return resultRequeue, kube.Status().Update(ctx, i) | |
} | |
- | |
-// fmtPersonaRole is a helper to ensure the persona role formatting parameters | |
-// are provided consistently | |
-func fmtPersonaRole(stack *v1alpha1.Stack, persona string) string { | |
- return fmt.Sprintf(clusterRoleNameFmt, stack.GetNamespace(), stack.GetName(), stack.Spec.Version, persona) | |
-} | |
diff --git a/pkg/stacks/clusterrole.go b/pkg/stacks/clusterrole.go | |
new file mode 100644 | |
index 00000000..64634da2 | |
--- /dev/null | |
+++ b/pkg/stacks/clusterrole.go | |
@@ -0,0 +1,53 @@ | |
+/* | |
+Copyright 2019 The Crossplane Authors. | |
+ | |
+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 stacks | |
+ | |
+import ( | |
+ "fmt" | |
+ | |
+ "github.com/crossplaneio/crossplane/apis/stacks/v1alpha1" | |
+) | |
+ | |
+// Labels used to track ownership across namespaces and scopes. | |
+const ( | |
+ // rbac.crossplane.io/aggregate-to-{scope}-{persona} | |
+ // {scope} is namespace or environment and may include "-default" | |
+ // persona is one of admin, edit, or view | |
+ LabelAggregateFmt = "rbac.crossplane.io/aggregate-to-%s-%s" | |
+ | |
+ // namespace.crossplane.io/{namespace} | |
+ LabelNamespaceFmt = "namespace.crossplane.io/%s" | |
+ | |
+ LabelScope = "crossplane.io/scope" | |
+ | |
+ // crossplane:ns:{namespace}:{persona} | |
+ NamespaceClusterRoleNameFmt = "crossplane:ns:%s:%s" | |
+) | |
+ | |
+// Crossplane ClusterRole Scopes | |
+const ( | |
+ NamespaceScoped = "namespace" | |
+ EnvironmentScoped = "environment" | |
+) | |
+ | |
+// PersonaRoleName is a helper to ensure the persona role formatting parameters | |
+// are provided consistently | |
+func PersonaRoleName(stack *v1alpha1.Stack, persona string) string { | |
+ const clusterRoleNameFmt = "stack:%s:%s:%s:%s" | |
+ | |
+ return fmt.Sprintf(clusterRoleNameFmt, stack.GetNamespace(), stack.GetName(), stack.Spec.Version, persona) | |
+} | |
diff --git a/pkg/stacks/relationship.go b/pkg/stacks/relationship.go | |
index 3fcefce2..51da86f6 100644 | |
--- a/pkg/stacks/relationship.go | |
+++ b/pkg/stacks/relationship.go | |
@@ -37,6 +37,7 @@ type KindlyIdentifier interface { | |
GetName() string | |
GetNamespace() string | |
GetUID() types.UID | |
+ | |
GroupVersionKind() schema.GroupVersionKind | |
} | |
diff --git a/pkg/stacks/unpack.go b/pkg/stacks/unpack.go | |
index 14f804ad..aada1c02 100644 | |
--- a/pkg/stacks/unpack.go | |
+++ b/pkg/stacks/unpack.go | |
@@ -73,7 +73,6 @@ const ( | |
// Stack CRD Labels | |
labelKubernetesManagedBy = "app.kubernetes.io/managed-by" | |
- labelScope = "crossplane.io/scope" | |
) | |
var ( | |
@@ -291,9 +290,9 @@ func (sp *StackPackage) AddCRD(path string, crd *apiextensions.CustomResourceDef | |
crd.ObjectMeta.Labels[labelKubernetesManagedBy] = "stack-manager" | |
if sp.IsNamespaced() { | |
- crd.ObjectMeta.Labels[labelScope] = "namespace" | |
+ crd.ObjectMeta.Labels[LabelScope] = NamespaceScoped | |
} else { | |
- crd.ObjectMeta.Labels[labelScope] = "environment" | |
+ crd.ObjectMeta.Labels[LabelScope] = EnvironmentScoped | |
} | |
crdGVK := schema.GroupVersionKind{ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment