Skip to content

Commit

Permalink
Admission schema updates
Browse files Browse the repository at this point in the history
* Make non-API Kind structures common types in Cedar Schema
* Look up each referenced type in an attribute to tell if its an entity or type

Signed-off-by: Micah Hausler <mhausler@amazon.com>
  • Loading branch information
micahhausler committed Nov 7, 2024
1 parent fbedd22 commit 3b8b4f5
Show file tree
Hide file tree
Showing 20 changed files with 10,313 additions and 11,495 deletions.
2,126 changes: 1,063 additions & 1,063 deletions cedarschema/k8s-full.cedarschema

Large diffs are not rendered by default.

18,951 changes: 8,794 additions & 10,157 deletions cedarschema/k8s-full.cedarschema.json

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions cmd/converter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,22 @@ func main() {
if err != nil {
klog.Fatalf("Error building kubernetes clientset: %v", err)
}
ctx := context.Background()

switch kind {
case "rolebinding":
var rbs *rbacv1.RoleBindingList = &rbacv1.RoleBindingList{
Items: []rbacv1.RoleBinding{},
}
if len(resourceNames) == 0 {
rbs, err = cs.RbacV1().RoleBindings("").List(context.TODO(), metav1.ListOptions{})
rbs, err = cs.RbacV1().RoleBindings("").List(ctx, metav1.ListOptions{})
if err != nil {
klog.Fatalf("Error listing ClusterRoleBindings: %v", err)
}

} else {
for _, resourceName := range resourceNames {
rb, err := cs.RbacV1().RoleBindings(*namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
rb, err := cs.RbacV1().RoleBindings(*namespace).Get(ctx, resourceName, metav1.GetOptions{})
if err != nil {
klog.Errorf("Error getting RoleBinding %s: %v. Skipping this one", resourceName, err)
continue
Expand All @@ -77,14 +78,14 @@ func main() {
var ruler convert.Ruler
switch binding.RoleRef.Kind {
case "ClusterRole":
cr, err := cs.RbacV1().ClusterRoles().Get(context.TODO(), binding.RoleRef.Name, metav1.GetOptions{})
cr, err := cs.RbacV1().ClusterRoles().Get(ctx, binding.RoleRef.Name, metav1.GetOptions{})
if err != nil {
klog.Errorf("Error getting ClusterRole %s: %v. Skipping this one", binding.RoleRef.Name, err)
continue
}
ruler = convert.NewClusterRoleRuler(*cr)
case "Role":
role, err := cs.RbacV1().Roles(binding.Namespace).Get(context.TODO(), binding.RoleRef.Name, metav1.GetOptions{})
role, err := cs.RbacV1().Roles(binding.Namespace).Get(ctx, binding.RoleRef.Name, metav1.GetOptions{})
if err != nil {
klog.Errorf("Error getting Role %s: %v. Skipping this one", binding.RoleRef.Name, err)
continue
Expand Down Expand Up @@ -123,13 +124,13 @@ func main() {
Items: []rbacv1.ClusterRoleBinding{},
}
if len(resourceNames) == 0 {
crbs, err = cs.RbacV1().ClusterRoleBindings().List(context.TODO(), metav1.ListOptions{})
crbs, err = cs.RbacV1().ClusterRoleBindings().List(ctx, metav1.ListOptions{})
if err != nil {
klog.Fatalf("Error listing ClusterRoleBindings: %v", err)
}
} else {
for _, resourceName := range resourceNames {
crb, err := cs.RbacV1().ClusterRoleBindings().Get(context.TODO(), resourceName, metav1.GetOptions{})
crb, err := cs.RbacV1().ClusterRoleBindings().Get(ctx, resourceName, metav1.GetOptions{})
if err != nil {
klog.Errorf("Error getting ClusterRoleBinding %s: %v. Skipping this one", resourceName, err)
continue
Expand All @@ -138,7 +139,7 @@ func main() {
}
}
for i, crb := range crbs.Items {
cr, err := cs.RbacV1().ClusterRoles().Get(context.TODO(), crb.RoleRef.Name, metav1.GetOptions{})
cr, err := cs.RbacV1().ClusterRoles().Get(ctx, crb.RoleRef.Name, metav1.GetOptions{})
if err != nil {
klog.Errorf("Error getting ClusterRole %s: %v. Skipping this one", crb.RoleRef.Name, err)
continue
Expand Down
10 changes: 9 additions & 1 deletion cmd/schema-generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ func main() {
continue
}
klog.InfoS("Converting schema for API", "api", v.Name, "version", v.Version)

// TODO: In order to find which Admission verbs apply to which resources:
// * Get the APIResourceList{} (/{k}) for a given API
// * In ModifySchemaForAPIVersion(),
// * Find the APIResourceList.resources[].Kind
// * Look for the corresponding admission verbs (delete/deletecollection/create/patch/update)
// * Figure out how to determine which subresource maps to CONNECT

err = convert.ModifySchemaForAPIVersion(openAPISpec, cedarschema, v.Name, v.Version, *actionNs)
if err != nil {
klog.ErrorS(err, "Failed to get convert to cedar schema for API, skipping", "api", v.Name, "version", v.Version)
Expand All @@ -131,7 +139,7 @@ func main() {
}
}
cedarschema.SortActionEntities()
// TODO: this is just here until we get real key/value map support
// TODO: ENTITY TAGS: this is just here until we get real key/value map support
schema.ModifyObjectMetaMaps(cedarschema)

data, err := json.MarshalIndent(cedarschema, "", "\t")
Expand Down
2 changes: 1 addition & 1 deletion internal/convert/clusterrole_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
var update = flag.Bool("update", false, "update testdata")

func TestClusterRoleBindingToCedar(t *testing.T) {
// TODO:
// TODO: Test case cleanup
// * read testcases from input files containing CRB and CR objects
// * add all of Kind's in-cluster crbs/crs
// * migrate custom policies defined below to input files
Expand Down
2 changes: 1 addition & 1 deletion internal/convert/role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

func TestRoleBindingToCedar(t *testing.T) {
// TODO:
// TODO: test case cleanup
// * read testcases from input files containing CRB and CR objects
// * add all of Kind's in-cluster roles/roleBindings
// * migrate custom policies defined below to input files
Expand Down
2 changes: 1 addition & 1 deletion internal/schema/admission.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package schema

// ModifyObjectMetaMaps modifies the ObjectMeta maps in the schema
//
// This is a hack until Cedar supports maps in the schema
// TODO: ENTITY TAGS: This is a hack until Cedar supports maps in the schema
func ModifyObjectMetaMaps(schema CedarSchema) {
ns, ok := schema["meta::v1"]
if !ok {
Expand Down
67 changes: 67 additions & 0 deletions internal/schema/convert/name_transform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package convert

import (
"slices"
"strings"
)

func ParseSchemaName(schemaName string) (ns, apiGroup, version, kind string) {
schemaName = strings.ReplaceAll(schemaName, "-", "_")
strs := strings.Split(schemaName, ".")
if len(strs) < 4 {
return
}
slices.Reverse(strs)

if strings.HasPrefix(schemaName, "io.k8s.api.") {
strs = strs[:len(strs)-3]
} else if strings.HasPrefix(schemaName, "io.k8s.apimachinery.pkg.apis.meta") {
strs = strs[:len(strs)-4]
} else {
nsParts := strs[3:]
slices.Reverse(nsParts)
ns = strings.Join(nsParts, "::")
}

kind = strs[0]
version = strs[1]
apiGroup = strs[2]
return
}

func SchemaNameToCedar(schemaName string) (ns, typeName string) {
ns, apiGroup, version, kind := ParseSchemaName(schemaName)
if ns != "" {
return strings.Join([]string{ns, apiGroup, version}, "::"), kind
}
return strings.Join([]string{apiGroup, version}, "::"), kind
}

// Transform `"#/components/schemas/io.k8s.api.apps.v1.DaemonSetSpec"`
// into `apps::v1::DaemonSetSpec`
func refToRelativeTypeName(current, ref string) string {
curParsed, found := strings.CutPrefix(current, "#/components/schemas/")
if !found {
curParsed = current
}
currentNs, _ := SchemaNameToCedar(curParsed)

refParsed, found := strings.CutPrefix(ref, "#/components/schemas/")
if !found {
refParsed = ref
}
refNs, refType := SchemaNameToCedar(refParsed)

if (refNs == "meta::v1" && refType == "Time") ||
(refNs == "meta::v1" && refType == "MicroTime") ||
(refNs == "io::k8s::apimachinery::pkg::util::intstr" && refType == "IntOrString") ||
(refNs == "io::k8s::apimachinery::pkg::api::resource" && refType == "Quantity") ||
(refNs == "io::k8s::apimachinery::pkg::runtime" && refType == "RawExtension") {
return "String"

Check failure on line 60 in internal/schema/convert/name_transform.go

View workflow job for this annotation

GitHub Actions / lint

string `String` has 3 occurrences, make it a constant (goconst)
}

if currentNs == refNs {
return refType
}
return refNs + "::" + refType
}
205 changes: 205 additions & 0 deletions internal/schema/convert/name_transform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package convert

import (
"testing"
)

func TestParseSchemaName(t *testing.T) {
testCases := []struct {
name string
intput string
wantNs, wantAPIGroup, wantVersion, wantKind string
}{
{
"DaemonSet",
"io.k8s.api.apps.v1.DaemonSet",
"",
"apps",
"v1",
"DaemonSet",
},
{
"ConfigMap",
"io.k8s.api.core.v1.ConfigMap",
"",
"core",
"v1",
"ConfigMap",
},
{
"Cedar Policy",
"aws.k8s.cedar.v1.Policy",
"aws::k8s",
"cedar",
"v1",
"Policy",
},
{
"too short",
"aws.cedar.v1",
"",
"",
"",
"",
},
{
"Object meta",
"io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta",
"",
"meta",
"v1",
"ObjectMeta",
},
{
"CRD",
"io.cert-manager.v1.ClusterIssuer",
"io",
"cert_manager",
"v1",
"ClusterIssuer",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gotNs, gotAPIGroup, gotVersion, gotKind := ParseSchemaName(tc.intput)
if gotNs != tc.wantNs {
t.Fatalf("unexpected ns: got %q, want %q", gotNs, tc.wantNs)
}
if gotAPIGroup != tc.wantAPIGroup {
t.Fatalf("unexpected apigroup: got %q, want %q", gotAPIGroup, tc.wantAPIGroup)
}
if gotVersion != tc.wantVersion {
t.Fatalf("unexpected version: got %q, want %q", gotVersion, tc.wantVersion)
}
if gotKind != tc.wantKind {
t.Fatalf("unexpected kind: got %q, want %q", gotKind, tc.wantKind)
}
})
}
}

func TestSchemaNameToCedar(t *testing.T) {
cases := []struct {
name string
input string
wantNs, wantName string
}{
{
"K8s auth api",
"io.k8s.api.authentication.v1.TokenRequest",
"authentication::v1",
"TokenRequest",
},
{
"K8s autoscaling API",
"io.k8s.api.autoscaling.v1.Scale",
"autoscaling::v1",
"Scale",
},
{
"core K8s api",
"io.k8s.api.core.v1.ConfigMap",
"core::v1",
"ConfigMap",
},
{
"coordination K8s api",
"io.k8s.api.coordination.v1.Lease",
"coordination::v1",
"Lease",
},
{
"k8s meta API",
"io.k8s.apimachinery.pkg.apis.meta.v1.Status",
"meta::v1",
"Status",
},
// Should never get this case, we filter it out elsewhere
// {
// "k8s resoruce API",
// "io.k8s.apimachinery.pkg.api.resource.Quantity",
// "api::resource",
// "Quantity",
// },
{
"Cedar policy resource",
"aws.k8s.cedar.v1.Policy",
"aws::k8s::cedar::v1",
"Policy",
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
gotNs, gotName := SchemaNameToCedar(tc.input)
if gotNs != tc.wantNs {
t.Fatalf("unexpected ns: got %q, want %q", gotNs, tc.wantNs)
}
if gotName != tc.wantName {
t.Fatalf("unexpected name: got %q, want %q", gotName, tc.wantName)
}
})
}
}

func TestRefToRelativeTypeName(t *testing.T) {
cases := []struct {
name, input, currentNs, want string
}{
{
name: "CRD",
input: `#/components/schemas/aws.k8s.cedar.v1.Policy`,
want: "aws::k8s::cedar::v1::Policy",
},
{
name: "K8s API",
input: `#/components/schemas/io.k8s.api.core.v1.ConfigMap`,
want: "core::v1::ConfigMap",
},
{
name: "Meta API",
input: `#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta`,
want: "meta::v1::ObjectMeta",
},
{
name: "in-tree API group",
input: `#/components/schemas/io.k8s.api.apps.v1.DaemonSet`,
want: "apps::v1::DaemonSet",
},
{
name: "in-tree API group, same namespace",
input: `#/components/schemas/io.k8s.api.core.v1.PodSpec`,
currentNs: "#/components/schemas/io.k8s.api.core.v1.Pod",
want: "PodSpec",
},
{
name: "Time to string",
input: `#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time`,
want: "String",
},
{
name: "MicroTime to string",
input: `#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime`,
want: "String",
},
{
name: "Quantity to string",
input: `#/components/schemas/io.k8s.apimachinery.pkg.api.resource.Quantity`,
want: "String",
},
{
name: "RawExtension to String",
input: `#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension`,
want: "String",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := refToRelativeTypeName(tc.currentNs, tc.input)
if got != tc.want {
t.Fatalf("unexpected output: got %q, want %q", got, tc.want)
}
})
}
}
Loading

0 comments on commit 3b8b4f5

Please sign in to comment.