diff --git a/docs/docs/100-Reference/01-command-line/acorn_app.md b/docs/docs/100-Reference/01-command-line/acorn_app.md index 82b041d29..da8d0cde0 100644 --- a/docs/docs/100-Reference/01-command-line/acorn_app.md +++ b/docs/docs/100-Reference/01-command-line/acorn_app.md @@ -19,6 +19,7 @@ acorn app ### Options ``` + -a, --all Include stopped apps -h, --help help for app -o, --output string Output format (json, yaml, {{gotemplate}}) -q, --quiet Output only names diff --git a/docs/docs/100-Reference/01-command-line/acorn_dev.md b/docs/docs/100-Reference/01-command-line/acorn_dev.md index 9bb4cff44..6d56d0b83 100644 --- a/docs/docs/100-Reference/01-command-line/acorn_dev.md +++ b/docs/docs/100-Reference/01-command-line/acorn_dev.md @@ -16,6 +16,7 @@ acorn dev [flags] DIRECTORY ### Options ``` + --dangerous Automatically approve all privileges requested by the application -d, --dns strings Assign a friendly domain to a published container (format public:private) (ex: example.com:web) -f, --file string Name of the dev file (default "DIRECTORY/acorn.cue") -h, --help help for dev diff --git a/docs/docs/100-Reference/01-command-line/acorn_dev_render.md b/docs/docs/100-Reference/01-command-line/acorn_dev_render.md index 671612a2f..789b869fe 100644 --- a/docs/docs/100-Reference/01-command-line/acorn_dev_render.md +++ b/docs/docs/100-Reference/01-command-line/acorn_dev_render.md @@ -20,6 +20,7 @@ acorn dev render [flags] DIRECTORY ``` -A, --all-namespaces Namespace to work in --context string Context to use in the kubeconfig file + --dangerous Automatically approve all privileges requested by the application -d, --dns strings Assign a friendly domain to a published container (format public:private) (ex: example.com:web) -f, --file string Name of the dev file (default "DIRECTORY/acorn.cue") --kubeconfig string Location of a kubeconfig file diff --git a/docs/docs/100-Reference/01-command-line/acorn_run.md b/docs/docs/100-Reference/01-command-line/acorn_run.md index ccf34c1ff..330aa7426 100644 --- a/docs/docs/100-Reference/01-command-line/acorn_run.md +++ b/docs/docs/100-Reference/01-command-line/acorn_run.md @@ -12,6 +12,7 @@ acorn run [flags] IMAGE [deploy flags] ### Options ``` + --dangerous Automatically approve all privileges requested by the application -d, --dns strings Assign a friendly domain to a published container (format public:private) (ex: example.com:web) -h, --help help for run -i, --interactive Stream logs/status in the foreground and stop on exit diff --git a/docs/docs/100-Reference/01-command-line/acorn_update.md b/docs/docs/100-Reference/01-command-line/acorn_update.md index 78a025f99..a69e7b9a9 100644 --- a/docs/docs/100-Reference/01-command-line/acorn_update.md +++ b/docs/docs/100-Reference/01-command-line/acorn_update.md @@ -12,6 +12,7 @@ acorn update [flags] APP_NAME [deploy flags] ### Options ``` + --dangerous Automatically approve all privileges requested by the application -d, --dns strings Assign a friendly domain to a published container (format public:private) (ex: example.com:web) -h, --help help for update --image string diff --git a/generate.go b/generate.go index 3aff69029..e02d50974 100644 --- a/generate.go +++ b/generate.go @@ -1,7 +1,7 @@ //go:generate go run github.com/acorn-io/baaah/cmd/deepcopy ./pkg/apis/internal.acorn.io/v1/ //go:generate go run github.com/acorn-io/baaah/cmd/deepcopy ./pkg/apis/api.acorn.io/v1/ //go:generate go run github.com/acorn-io/baaah/cmd/deepcopy ./pkg/apis/ui.acorn.io/v1/ -//go:generate go run k8s.io/kube-openapi/cmd/openapi-gen -i github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1,github.com/acorn-io/acorn/pkg/apis/api.acorn.io/v1,k8s.io/apimachinery/pkg/apis/meta/v1,k8s.io/apimachinery/pkg/runtime,k8s.io/apimachinery/pkg/version,k8s.io/apimachinery/pkg/api/resource,k8s.io/api/core/v1 -p ./pkg/openapi/generated -h tools/header.txt +//go:generate go run k8s.io/kube-openapi/cmd/openapi-gen -i github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1,github.com/acorn-io/acorn/pkg/apis/api.acorn.io/v1,k8s.io/apimachinery/pkg/apis/meta/v1,k8s.io/apimachinery/pkg/runtime,k8s.io/apimachinery/pkg/version,k8s.io/apimachinery/pkg/api/resource,k8s.io/api/core/v1,k8s.io/api/rbac/v1 -p ./pkg/openapi/generated -h tools/header.txt //#go:generate go run k8s.io/code-generator/cmd/conversion-gen -i github.com/acorn-io/acorn/pkg/apis/api.acorn.io/v1 -p ./pkg/test/generated -h tools/header.txt package main diff --git a/go.mod b/go.mod index 0c9ab3be8..6fb134bac 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ replace ( require ( cuelang.org/go v0.4.3 github.com/AlecAivazis/survey/v2 v2.3.5 - github.com/acorn-io/baaah v0.0.0-20220627023500-fb2314473b8e + github.com/acorn-io/baaah v0.0.0-20220627212647-64d94b77b711 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.6.6 github.com/google/go-containerregistry v0.10.0 diff --git a/go.sum b/go.sum index 75d21926f..fdcea322c 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/acorn-io/apiserver v0.24.1-ot-1 h1:HPyswxxeEMq2gywsyQo+/fknhhl50SErvq github.com/acorn-io/apiserver v0.24.1-ot-1/go.mod h1:J41BYwfMMj7Mm6OfFX3mup3myklBebjEE5mfL6zm/Jg= github.com/acorn-io/apiserver-1 v0.0.0-20220608053213-0ffc3be57697 h1:zEzrL1ewSmEJSYdHamsxBO9JAf/z+xBC9mSq0oH6jkA= github.com/acorn-io/apiserver-1 v0.0.0-20220608053213-0ffc3be57697/go.mod h1:sG6OmZ4yEWeQ9JmGjnp8WgQAk9D9z4hivMFsUUh9QF8= -github.com/acorn-io/baaah v0.0.0-20220627023500-fb2314473b8e h1:rR0fwLnWYZaNuwI8D0rcGYfS84DpdyQPmWnrs0QY9Vo= -github.com/acorn-io/baaah v0.0.0-20220627023500-fb2314473b8e/go.mod h1:CSj9RfR9Ab5LsLmdAcJlrMtyz0tD1kfqAKiLnbkXDg0= +github.com/acorn-io/baaah v0.0.0-20220627212647-64d94b77b711 h1:BmjCjeLZJDPhqn/+Nv6D1GIZ/1xD3kcidCOpO5GbtVc= +github.com/acorn-io/baaah v0.0.0-20220627212647-64d94b77b711/go.mod h1:CSj9RfR9Ab5LsLmdAcJlrMtyz0tD1kfqAKiLnbkXDg0= github.com/acorn-io/component-base v0.24.1-ot-1 h1:GRbiCcCxZdKovA1L8eLFIvI9pJp3kJFOtKTOW7LSsQI= github.com/acorn-io/component-base v0.24.1-ot-1/go.mod h1:GLGWZB2NbdO0JXuNHLQ4IOAYnugwAXGRNmYxnb2GeKw= github.com/acorn-io/etcd/server/v3 v3.5.1-ot-1 h1:MlyJCGYCmK9g7y3qjRQChBjDRLcjuHgxDG+BBM6+IVI= diff --git a/pkg/apis/internal.acorn.io/v1/appinstance.go b/pkg/apis/internal.acorn.io/v1/appinstance.go index 460f98277..b6e04b1d7 100644 --- a/pkg/apis/internal.acorn.io/v1/appinstance.go +++ b/pkg/apis/internal.acorn.io/v1/appinstance.go @@ -48,6 +48,7 @@ type AppInstanceSpec struct { PublishProtocols []Protocol `json:"publishProtocols,omitempty"` Ports []PortBinding `json:"ports,omitempty"` DeployArgs GenericMap `json:"deployArgs,omitempty"` + Permissions *Permissions `json:"permissions,omitempty"` } func (in AppInstanceSpec) GetDevMode() bool { diff --git a/pkg/apis/internal.acorn.io/v1/appspec.go b/pkg/apis/internal.acorn.io/v1/appspec.go index 44ff79259..a7dc50dcb 100644 --- a/pkg/apis/internal.acorn.io/v1/appspec.go +++ b/pkg/apis/internal.acorn.io/v1/appspec.go @@ -1,5 +1,7 @@ package v1 +import rbacv1 "k8s.io/api/rbac/v1" + const ( VolumeRequestTypeEphemeral = "ephemeral" @@ -142,6 +144,25 @@ type Dependency struct { TargetName string `json:"targetName,omitempty"` } +type Permissions struct { + Rules []rbacv1.PolicyRule `json:"rules,omitempty"` + ClusterRules []rbacv1.PolicyRule `json:"clusterRules,omitempty"` +} + +func (in *Permissions) HasRules() bool { + if in == nil { + return false + } + return len(in.ClusterRules) > 0 || len(in.Rules) > 0 +} + +func (in *Permissions) Get() Permissions { + if in == nil { + return Permissions{} + } + return *in +} + type Container struct { Dirs map[string]VolumeMount `json:"dirs,omitempty"` Files map[string]File `json:"files,omitempty"` @@ -155,6 +176,7 @@ type Container struct { Ports []PortDef `json:"ports,omitempty"` Probes []Probe `json:"probes,omitempty"` Dependencies []Dependency `json:"dependencies,omitempty"` + Permissions *Permissions `json:"permissions,omitempty"` // Scale is only available on containers, not sidecars or jobs Scale *int32 `json:"scale,omitempty"` @@ -187,13 +209,16 @@ type AppSpec struct { } type Acorn struct { - Image string `json:"image,omitempty"` - Build *AcornBuild `json:"build,omitempty"` - DeployArgs GenericMap `json:"deployArgs,omitempty"` - Ports []PortDef `json:"ports,omitempty"` - Secrets []SecretBinding `json:"secrets,omitempty"` - Volumes []VolumeBinding `json:"volumes,omitempty"` - Services []ServiceBinding `json:"services,omitempty"` + Image string `json:"image,omitempty"` + Build *AcornBuild `json:"build,omitempty"` + DeployArgs GenericMap `json:"deployArgs,omitempty"` + Ports []PortDef `json:"ports,omitempty"` + Secrets []SecretBinding `json:"secrets,omitempty"` + Volumes []VolumeBinding `json:"volumes,omitempty"` + Services []ServiceBinding `json:"services,omitempty"` + Roles []rbacv1.PolicyRule `json:"roles,omitempty"` + ClusterRoles []rbacv1.PolicyRule `json:"clusterRoles,omitempty"` + Permissions *Permissions `json:"permissions,omitempty"` } type Secret struct { diff --git a/pkg/apis/internal.acorn.io/v1/zz_generated.deepcopy.go b/pkg/apis/internal.acorn.io/v1/zz_generated.deepcopy.go index 394497832..b057d86ab 100644 --- a/pkg/apis/internal.acorn.io/v1/zz_generated.deepcopy.go +++ b/pkg/apis/internal.acorn.io/v1/zz_generated.deepcopy.go @@ -6,6 +6,7 @@ package v1 import ( + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -40,6 +41,25 @@ func (in *Acorn) DeepCopyInto(out *Acorn) { *out = make([]ServiceBinding, len(*in)) copy(*out, *in) } + if in.Roles != nil { + in, out := &in.Roles, &out.Roles + *out = make([]rbacv1.PolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ClusterRoles != nil { + in, out := &in.ClusterRoles, &out.ClusterRoles + *out = make([]rbacv1.PolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = new(Permissions) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Acorn. @@ -245,6 +265,11 @@ func (in *AppInstanceSpec) DeepCopyInto(out *AppInstanceSpec) { copy(*out, *in) } out.DeployArgs = in.DeployArgs.DeepCopy() + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = new(Permissions) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppInstanceSpec. @@ -507,6 +532,11 @@ func (in *Container) DeepCopyInto(out *Container) { *out = make([]Dependency, len(*in)) copy(*out, *in) } + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = new(Permissions) + (*in).DeepCopyInto(*out) + } if in.Scale != nil { in, out := &in.Scale, &out.Scale *out = new(int32) @@ -908,6 +938,35 @@ func (in *ParamSpec) DeepCopy() *ParamSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Permissions) DeepCopyInto(out *Permissions) { + *out = *in + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]rbacv1.PolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ClusterRules != nil { + in, out := &in.ClusterRules, &out.ClusterRules + *out = make([]rbacv1.PolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permissions. +func (in *Permissions) DeepCopy() *Permissions { + if in == nil { + return nil + } + out := new(Permissions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Platform) DeepCopyInto(out *Platform) { *out = *in diff --git a/pkg/appdefinition/appdefinition_test.go b/pkg/appdefinition/appdefinition_test.go index dd0f90fa0..8a0d98aab 100644 --- a/pkg/appdefinition/appdefinition_test.go +++ b/pkg/appdefinition/appdefinition_test.go @@ -1751,3 +1751,81 @@ profiles: foo: build: {} _, _, err = def.WithDeployArgs(nil, []string{"missing"}) assert.Equal(t, "failed to find deploy profile missing", err.Error()) } + +func TestPermissions(t *testing.T) { + acornCue := ` +localData: permissions: { + rules: [ + { + verbs: ["verb"] + apiGroups: ["groups"] + resources: ["resources"] + resourceNames: ["names"] + nonResourceURLs: ["foo"] + } + ] + clusterRules: [ + { + verbs: ["verb"] + apiGroups: ["groups"] + resources: ["resources"] + resourceNames: ["names"] + nonResourceURLs: ["foo"] + } + ] +} + +containers: cont: { + permissions: localData.permissions + sidecars: side: permissions: localData.permissions +} + +acorns: acorn: permissions: localData.permissions +` + + def, err := NewAppDefinition([]byte(acornCue)) + if err != nil { + t.Fatal(err) + } + + appSpec, err := def.AppSpec() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "verb", appSpec.Containers["cont"].Permissions.Rules[0].Verbs[0]) + assert.Equal(t, "groups", appSpec.Containers["cont"].Permissions.Rules[0].APIGroups[0]) + assert.Equal(t, "resources", appSpec.Containers["cont"].Permissions.Rules[0].Resources[0]) + assert.Equal(t, "names", appSpec.Containers["cont"].Permissions.Rules[0].ResourceNames[0]) + assert.Equal(t, "foo", appSpec.Containers["cont"].Permissions.Rules[0].NonResourceURLs[0]) + + assert.Equal(t, "verb", appSpec.Containers["cont"].Permissions.ClusterRules[0].Verbs[0]) + assert.Equal(t, "groups", appSpec.Containers["cont"].Permissions.ClusterRules[0].APIGroups[0]) + assert.Equal(t, "resources", appSpec.Containers["cont"].Permissions.ClusterRules[0].Resources[0]) + assert.Equal(t, "names", appSpec.Containers["cont"].Permissions.ClusterRules[0].ResourceNames[0]) + assert.Equal(t, "foo", appSpec.Containers["cont"].Permissions.ClusterRules[0].NonResourceURLs[0]) + + assert.Equal(t, "verb", appSpec.Containers["cont"].Sidecars["side"].Permissions.Rules[0].Verbs[0]) + assert.Equal(t, "groups", appSpec.Containers["cont"].Sidecars["side"].Permissions.Rules[0].APIGroups[0]) + assert.Equal(t, "resources", appSpec.Containers["cont"].Sidecars["side"].Permissions.Rules[0].Resources[0]) + assert.Equal(t, "names", appSpec.Containers["cont"].Sidecars["side"].Permissions.Rules[0].ResourceNames[0]) + assert.Equal(t, "foo", appSpec.Containers["cont"].Sidecars["side"].Permissions.Rules[0].NonResourceURLs[0]) + + assert.Equal(t, "verb", appSpec.Containers["cont"].Sidecars["side"].Permissions.ClusterRules[0].Verbs[0]) + assert.Equal(t, "groups", appSpec.Containers["cont"].Sidecars["side"].Permissions.ClusterRules[0].APIGroups[0]) + assert.Equal(t, "resources", appSpec.Containers["cont"].Sidecars["side"].Permissions.ClusterRules[0].Resources[0]) + assert.Equal(t, "names", appSpec.Containers["cont"].Sidecars["side"].Permissions.ClusterRules[0].ResourceNames[0]) + assert.Equal(t, "foo", appSpec.Containers["cont"].Sidecars["side"].Permissions.ClusterRules[0].NonResourceURLs[0]) + + assert.Equal(t, "verb", appSpec.Acorns["acorn"].Permissions.Rules[0].Verbs[0]) + assert.Equal(t, "groups", appSpec.Acorns["acorn"].Permissions.Rules[0].APIGroups[0]) + assert.Equal(t, "resources", appSpec.Acorns["acorn"].Permissions.Rules[0].Resources[0]) + assert.Equal(t, "names", appSpec.Acorns["acorn"].Permissions.Rules[0].ResourceNames[0]) + assert.Equal(t, "foo", appSpec.Acorns["acorn"].Permissions.Rules[0].NonResourceURLs[0]) + + assert.Equal(t, "verb", appSpec.Acorns["acorn"].Permissions.ClusterRules[0].Verbs[0]) + assert.Equal(t, "groups", appSpec.Acorns["acorn"].Permissions.ClusterRules[0].APIGroups[0]) + assert.Equal(t, "resources", appSpec.Acorns["acorn"].Permissions.ClusterRules[0].Resources[0]) + assert.Equal(t, "names", appSpec.Acorns["acorn"].Permissions.ClusterRules[0].ResourceNames[0]) + assert.Equal(t, "foo", appSpec.Acorns["acorn"].Permissions.ClusterRules[0].NonResourceURLs[0]) +} diff --git a/pkg/cli/apps.go b/pkg/cli/apps.go index 579fa801b..63cfba9ff 100644 --- a/pkg/cli/apps.go +++ b/pkg/cli/apps.go @@ -21,6 +21,7 @@ acorn app`, } type App struct { + All bool `usage:"Include stopped apps" short:"a"` Quiet bool `usage:"Output only names" short:"q"` Output string `usage:"Output format (json, yaml, {{gotemplate}})" short:"o"` } @@ -48,6 +49,9 @@ func (a *App) Run(cmd *cobra.Command, args []string) error { } for _, app := range apps { + if app.Status.Stopped && !a.All { + continue + } if len(args) > 0 { if slices.Contains(args, app.Name) { out.Write(app) diff --git a/pkg/cli/dev.go b/pkg/cli/dev.go index e170e979b..d6182f3b6 100644 --- a/pkg/cli/dev.go +++ b/pkg/cli/dev.go @@ -49,6 +49,7 @@ func (s *Dev) Run(cmd *cobra.Command, args []string) error { Cwd: cwd, Profiles: opts.Profiles, }, - Run: opts, + Run: opts, + Dangerous: s.Dangerous, }) } diff --git a/pkg/cli/info.go b/pkg/cli/info.go index 94fc7f72c..d67f284a6 100644 --- a/pkg/cli/info.go +++ b/pkg/cli/info.go @@ -7,6 +7,7 @@ import ( "github.com/acorn-io/acorn/pkg/client" "github.com/acorn-io/acorn/pkg/tables" "github.com/acorn-io/acorn/pkg/version" + bversion "github.com/acorn-io/baaah/pkg/version" "github.com/spf13/cobra" ) @@ -26,7 +27,7 @@ type Info struct { type ClientServerVersion struct { Client struct { - Version version.Version `json:"version,omitempty"` + Version bversion.Version `json:"version,omitempty"` } `json:"client,omitempty"` Server apiv1.InfoSpec `json:"server,omitempty"` } @@ -45,7 +46,7 @@ func (s *Info) Run(cmd *cobra.Command, args []string) error { out := table.NewWriter(tables.Info, "", false, s.Output) out.Write(ClientServerVersion{ Client: struct { - Version version.Version `json:"version,omitempty"` + Version bversion.Version `json:"version,omitempty"` }{Version: version.Get()}, Server: info.Spec, }) diff --git a/pkg/cli/run.go b/pkg/cli/run.go index e824006bd..654f36464 100644 --- a/pkg/cli/run.go +++ b/pkg/cli/run.go @@ -10,6 +10,7 @@ import ( "github.com/acorn-io/acorn/pkg/client" "github.com/acorn-io/acorn/pkg/deployargs" "github.com/acorn-io/acorn/pkg/dev" + "github.com/acorn-io/acorn/pkg/rulerequest" "github.com/acorn-io/acorn/pkg/run" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -40,6 +41,7 @@ type RunArgs struct { PublishAll bool `usage:"Publish all exposed ports of application" short:"P"` Publish []string `usage:"Publish exposed port of application (format [public:]private) (ex 81:80)" short:"p"` Profile []string `usage:"Profile to assign default values"` + Dangerous bool `usage:"Automatically approve all privileges requested by the application"` } func (s RunArgs) ToOpts() (client.AppRunOptions, error) { @@ -109,7 +111,7 @@ func (s *Run) Run(cmd *cobra.Command, args []string) error { opts.DeployArgs = deployParams - app, err := c.AppRun(cmd.Context(), image, &opts) + app, err := rulerequest.PromptRun(cmd.Context(), c, s.Dangerous, image, opts) if err != nil { return err } diff --git a/pkg/cli/update.go b/pkg/cli/update.go index 72b80874a..b1718391c 100644 --- a/pkg/cli/update.go +++ b/pkg/cli/update.go @@ -6,6 +6,7 @@ import ( cli "github.com/acorn-io/acorn/pkg/cli/builder" "github.com/acorn-io/acorn/pkg/client" "github.com/acorn-io/acorn/pkg/deployargs" + "github.com/acorn-io/acorn/pkg/rulerequest" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -64,7 +65,7 @@ func (s *Update) Run(cmd *cobra.Command, args []string) error { opts.Image = image opts.DeployArgs = deployParams - app, err := c.AppUpdate(cmd.Context(), name, &opts) + app, err := rulerequest.PromptUpdate(cmd.Context(), c, s.Dangerous, name, opts) if err != nil { return err } diff --git a/pkg/client/app.go b/pkg/client/app.go index 063a711d6..956b50946 100644 --- a/pkg/client/app.go +++ b/pkg/client/app.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "sort" + "strings" apiv1 "github.com/acorn-io/acorn/pkg/apis/api.acorn.io/v1" v1 "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1" @@ -13,7 +14,7 @@ import ( "github.com/acorn-io/baaah/pkg/typed" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - client2 "sigs.k8s.io/controller-runtime/pkg/client" + kclient "sigs.k8s.io/controller-runtime/pkg/client" ) func (c *client) AppRun(ctx context.Context, image string, opts *AppRunOptions) (*apiv1.App, error) { @@ -40,11 +41,12 @@ func (c *client) AppRun(ctx context.Context, image string, opts *AppRunOptions) Ports: opts.Ports, Profiles: opts.Profiles, DevMode: opts.DevMode, + Permissions: opts.Permissions, }, } ) - return app, c.Client.Create(ctx, app) + return app, translatePermissions(c.Client.Create(ctx, app)) } func (c *client) AppUpdate(ctx context.Context, name string, opts *AppUpdateOptions) (*apiv1.App, error) { @@ -78,8 +80,27 @@ func (c *client) AppUpdate(ctx context.Context, name string, opts *AppUpdateOpti if opts.DevMode != nil { app.Spec.DevMode = opts.DevMode } + if opts.Permissions != nil { + app.Spec.Permissions = opts.Permissions + } + + return app, translatePermissions(c.Client.Update(ctx, app)) +} - return app, c.Client.Update(ctx, app) +func translatePermissions(err error) error { + if err == nil { + return err + } + if strings.HasPrefix(err.Error(), PrefixErrRulesNeeded) { + perms := v1.Permissions{} + marshalErr := json.Unmarshal([]byte(strings.TrimPrefix(err.Error(), PrefixErrRulesNeeded)), &perms) + if marshalErr == nil { + return &ErrRulesNeeded{ + Permissions: perms, + } + } + } + return err } func (c *client) AppLog(ctx context.Context, name string, opts *LogOptions) (<-chan apiv1.LogMessage, error) { @@ -236,7 +257,7 @@ func (c *client) AppDelete(ctx context.Context, name string) (*apiv1.App, error) func (c *client) AppGet(ctx context.Context, name string) (*apiv1.App, error) { app := &apiv1.App{} - err := c.Client.Get(ctx, client2.ObjectKey{ + err := c.Client.Get(ctx, kclient.ObjectKey{ Name: name, Namespace: c.Namespace, }, app) @@ -249,7 +270,7 @@ func (c *client) AppGet(ctx context.Context, name string) (*apiv1.App, error) { func (c *client) AppList(ctx context.Context) ([]apiv1.App, error) { apps := &apiv1.AppList{} - err := c.Client.List(ctx, apps, &client2.ListOptions{ + err := c.Client.List(ctx, apps, &kclient.ListOptions{ Namespace: c.Namespace, }) if err != nil { @@ -268,7 +289,7 @@ func (c *client) AppList(ctx context.Context) ([]apiv1.App, error) { func (c *client) AppStart(ctx context.Context, name string) error { app := &apiv1.App{} - err := c.Client.Get(ctx, client2.ObjectKey{ + err := c.Client.Get(ctx, kclient.ObjectKey{ Name: name, Namespace: c.Namespace, }, app) @@ -284,7 +305,7 @@ func (c *client) AppStart(ctx context.Context, name string) error { func (c *client) AppStop(ctx context.Context, name string) error { app := &apiv1.App{} - err := c.Client.Get(ctx, client2.ObjectKey{ + err := c.Client.Get(ctx, kclient.ObjectKey{ Name: name, Namespace: c.Namespace, }, app) diff --git a/pkg/client/client.go b/pkg/client/client.go index 2154dbd31..62657744e 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -72,6 +72,7 @@ type AppUpdateOptions struct { Ports []v1.PortBinding PublishProtocols []v1.Protocol Profiles []string + Permissions *v1.Permissions DeployArgs map[string]interface{} DevMode *bool Image string @@ -92,6 +93,7 @@ type AppRunOptions struct { Profiles []string DeployArgs map[string]interface{} DevMode *bool + Permissions *v1.Permissions } func (a AppRunOptions) ToUpdate() AppUpdateOptions { @@ -107,6 +109,7 @@ func (a AppRunOptions) ToUpdate() AppUpdateOptions { DeployArgs: a.DeployArgs, DevMode: a.DevMode, Profiles: a.Profiles, + Permissions: a.Permissions, } } diff --git a/pkg/client/error.go b/pkg/client/error.go new file mode 100644 index 000000000..67da6fef2 --- /dev/null +++ b/pkg/client/error.go @@ -0,0 +1,37 @@ +package client + +import ( + "encoding/json" + "fmt" + + v1 "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1" + rbacv1 "k8s.io/api/rbac/v1" +) + +var ( + PrefixErrRulesNeeded = "rules needed: " +) + +type ErrRulesNeeded struct { + Permissions v1.Permissions +} + +func (e *ErrRulesNeeded) Error() string { + perms, err := json.Marshal(e.Permissions) + if err != nil { + panic(err) + } + return fmt.Sprintf("%s%s", PrefixErrRulesNeeded, perms) +} + +type ErrNotAuthorized struct { + Rule rbacv1.PolicyRule +} + +func (e *ErrNotAuthorized) Error() string { + perms, err := json.Marshal(e.Rule) + if err != nil { + panic(err) + } + return fmt.Sprintf("not authorized: %s", perms) +} diff --git a/pkg/controller/appdefinition/acorn.go b/pkg/controller/appdefinition/acorn.go index 762f120d2..9ef573714 100644 --- a/pkg/controller/appdefinition/acorn.go +++ b/pkg/controller/appdefinition/acorn.go @@ -52,7 +52,7 @@ func toAcorn(appInstance *v1.AppInstance, tag name.Reference, pullSecrets *PullS publishPorts := ports.RemapForBinding(true, acorn.Ports, appInstance.Spec.Ports, appInstance.Spec.PublishProtocols) ports := append(typed.MapSlice(acorn.Ports, toNonPublishPortBinding), typed.MapSlice(publishPorts, toPublishPortBinding)...) - return &v1.AppInstance{ + acornInstance := &v1.AppInstance{ ObjectMeta: metav1.ObjectMeta{ Name: acornName, Namespace: appInstance.Status.Namespace, @@ -70,4 +70,10 @@ func toAcorn(appInstance *v1.AppInstance, tag name.Reference, pullSecrets *PullS Ports: ports, }, } + + if acorn.Permissions.HasRules() { + acornInstance.Spec.Permissions = appInstance.Spec.Permissions + } + + return acornInstance } diff --git a/pkg/controller/appdefinition/deploy.go b/pkg/controller/appdefinition/deploy.go index cee276cd5..0ab3a2477 100644 --- a/pkg/controller/appdefinition/deploy.go +++ b/pkg/controller/appdefinition/deploy.go @@ -77,6 +77,7 @@ func DeploySpec(req router.Request, resp router.Response) (err error) { } addNamespace(cfg, appInstance, resp) + addServiceAccount(appInstance, resp) if err := addDeployments(req, appInstance, tag, pullSecrets, resp); err != nil { return err } @@ -554,17 +555,37 @@ func toDeployment(req router.Request, appInstance *v1.AppInstance, tag name.Refe }, }, } + if stateful { dep.Spec.Replicas = &[]int32{1}[0] dep.Spec.Template.Spec.Hostname = dep.Name dep.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType } + if appInstance.Spec.Stop != nil && *appInstance.Spec.Stop { dep.Spec.Replicas = new(int32) } + + if needsServiceAccount(container) { + dep.Spec.Template.Spec.ServiceAccountName = "acorn" + dep.Spec.Template.Spec.AutomountServiceAccountToken = &[]bool{true}[0] + } + return dep, nil } +func needsServiceAccount(container v1.Container) bool { + if container.Permissions.HasRules() { + return true + } + for _, sidecar := range container.Sidecars { + if sidecar.Permissions.HasRules() { + return true + } + } + return false +} + func ToDeployments(req router.Request, appInstance *v1.AppInstance, tag name.Reference, pullSecrets *PullSecrets) (result []kclient.Object, _ error) { for _, entry := range typed.Sorted(appInstance.Status.AppSpec.Containers) { if isLinked(appInstance, entry.Key) { diff --git a/pkg/controller/appdefinition/jobs.go b/pkg/controller/appdefinition/jobs.go index 29320604b..3cf723b3a 100644 --- a/pkg/controller/appdefinition/jobs.go +++ b/pkg/controller/appdefinition/jobs.go @@ -80,6 +80,11 @@ func toJob(req router.Request, appInstance *v1.AppInstance, pullSecrets *PullSec }, } + if needsServiceAccount(container) { + jobSpec.Template.Spec.ServiceAccountName = "acorn" + jobSpec.Template.Spec.AutomountServiceAccountToken = &[]bool{true}[0] + } + if container.Schedule == "" { return &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/controller/appdefinition/serviceaccount.go b/pkg/controller/appdefinition/serviceaccount.go new file mode 100644 index 000000000..f5b7ce8e0 --- /dev/null +++ b/pkg/controller/appdefinition/serviceaccount.go @@ -0,0 +1,85 @@ +package appdefinition + +import ( + v1 "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1" + "github.com/acorn-io/acorn/pkg/labels" + "github.com/acorn-io/baaah/pkg/router" + "github.com/rancher/wrangler/pkg/name" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +func addServiceAccount(appInstance *v1.AppInstance, resp router.Response) { + resp.Objects(toServiceAccount(appInstance)...) +} + +func toServiceAccount(appInstance *v1.AppInstance) (result []kclient.Object) { + if !appInstance.Spec.Permissions.HasRules() { + return nil + } + + result = append(result, &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "acorn", + Namespace: appInstance.Status.Namespace, + Labels: labels.Managed(appInstance), + }, + }) + + if len(appInstance.Spec.Permissions.ClusterRules) > 0 { + name := name.SafeConcatName("acorn", appInstance.Name, appInstance.Namespace, string(appInstance.UID[:8])) + result = append(result, &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Rules: appInstance.Spec.Permissions.ClusterRules, + }, &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "acorn", + Namespace: appInstance.Status.Namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: name, + }, + }) + } + + if len(appInstance.Spec.Permissions.Rules) > 0 { + result = append(result, &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "acorn", + Namespace: appInstance.Status.Namespace, + }, + Rules: appInstance.Spec.Permissions.Rules, + }, &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "acorn", + Namespace: appInstance.Status.Namespace, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "acorn", + Namespace: appInstance.Status.Namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: "acorn", + }, + }) + } + + return result +} diff --git a/pkg/controller/appdefinition/testdata/parseappimage/expected.yaml b/pkg/controller/appdefinition/testdata/parseappimage/expected.yaml index 7b877ccd4..0a34de852 100644 --- a/pkg/controller/appdefinition/testdata/parseappimage/expected.yaml +++ b/pkg/controller/appdefinition/testdata/parseappimage/expected.yaml @@ -25,6 +25,7 @@ status: interactive: false files: {} volumes: [] + permissions: {} buildimage: dirs: {} image: "sha256:build-image" @@ -40,6 +41,7 @@ status: contextDirs: {} files: {} volumes: [] + permissions: {} appImage: acornfile: | containers: { diff --git a/pkg/dev/dev.go b/pkg/dev/dev.go index 154e63e53..488b69cd9 100644 --- a/pkg/dev/dev.go +++ b/pkg/dev/dev.go @@ -19,6 +19,7 @@ import ( "github.com/acorn-io/acorn/pkg/deployargs" "github.com/acorn-io/acorn/pkg/labels" "github.com/acorn-io/acorn/pkg/log" + "github.com/acorn-io/acorn/pkg/rulerequest" objwatcher "github.com/acorn-io/acorn/pkg/watcher" "github.com/pterm/pterm" "github.com/sirupsen/logrus" @@ -28,11 +29,12 @@ import ( ) type Options struct { - Args []string - Client client.Client - Build build.Options - Run client.AppRunOptions - Log client.LogOptions + Args []string + Client client.Client + Build build.Options + Run client.AppRunOptions + Log client.LogOptions + Dangerous bool } func (o *Options) Complete() (*Options, error) { @@ -171,7 +173,7 @@ func buildLoop(ctx context.Context, file string, opts *Options) error { app, err := runOrUpdate(ctx, file, image.ID, opts) if err != nil { - logrus.Errorf("Failed to run app: %v", err) + logrus.Errorf("Failed to run/update app: %v", err) continue } @@ -179,6 +181,7 @@ func buildLoop(ctx context.Context, file string, opts *Options) error { continue } + opts.Run.Name = app.Name LogLoop(ctx, opts.Client, app, &opts.Log) AppStatusLoop(ctx, opts.Client, app) containerSyncLoop(ctx, app, opts) @@ -201,7 +204,7 @@ func updateApp(ctx context.Context, c client.Client, app *apiv1.App, image strin } update := opts.Run.ToUpdate() update.Image = image - _, err := c.AppUpdate(ctx, app.Name, &update) + _, err := rulerequest.PromptUpdate(ctx, opts.Client, opts.Dangerous, app.Name, update) return err } @@ -216,7 +219,7 @@ func createApp(ctx context.Context, acornCue, image string, opts *Options) (*api } opts.Run.Annotations[labels.AcornAppCuePath] = acornCue - app, err := opts.Client.AppRun(ctx, image, &opts.Run) + app, err := rulerequest.PromptRun(ctx, opts.Client, opts.Dangerous, image, opts.Run) if err != nil { return nil, err } diff --git a/pkg/install/role.yaml b/pkg/install/role.yaml index 8bf4e03e1..0641badeb 100644 --- a/pkg/install/role.yaml +++ b/pkg/install/role.yaml @@ -43,6 +43,17 @@ rules: resources: - deployments - daemonsets + - verbs: ["create"] + apiGroups: ["authorization.k8s.io"] + resources: + - subjectaccessreviews + - verbs: ["*"] + apiGroups: ["rbac.authorization.k8s.io"] + resources: + - clusterroles + - roles + - clusterrolebindings + - rolebindings --- kind: ClusterRoleBinding diff --git a/pkg/openapi/generated/openapi_generated.go b/pkg/openapi/generated/openapi_generated.go index c1170454c..1f05cba67 100644 --- a/pkg/openapi/generated/openapi_generated.go +++ b/pkg/openapi/generated/openapi_generated.go @@ -84,6 +84,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.JobStatus": schema_pkg_apis_internalacornio_v1_JobStatus(ref), "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Param": schema_pkg_apis_internalacornio_v1_Param(ref), "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.ParamSpec": schema_pkg_apis_internalacornio_v1_ParamSpec(ref), + "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Permissions": schema_pkg_apis_internalacornio_v1_Permissions(ref), "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Platform": schema_pkg_apis_internalacornio_v1_Platform(ref), "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.PortBinding": schema_pkg_apis_internalacornio_v1_PortBinding(ref), "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.PortDef": schema_pkg_apis_internalacornio_v1_PortDef(ref), @@ -307,6 +308,18 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource": schema_k8sio_api_core_v1_VsphereVirtualDiskVolumeSource(ref), "k8s.io/api/core/v1.WeightedPodAffinityTerm": schema_k8sio_api_core_v1_WeightedPodAffinityTerm(ref), "k8s.io/api/core/v1.WindowsSecurityContextOptions": schema_k8sio_api_core_v1_WindowsSecurityContextOptions(ref), + "k8s.io/api/rbac/v1.AggregationRule": schema_k8sio_api_rbac_v1_AggregationRule(ref), + "k8s.io/api/rbac/v1.ClusterRole": schema_k8sio_api_rbac_v1_ClusterRole(ref), + "k8s.io/api/rbac/v1.ClusterRoleBinding": schema_k8sio_api_rbac_v1_ClusterRoleBinding(ref), + "k8s.io/api/rbac/v1.ClusterRoleBindingList": schema_k8sio_api_rbac_v1_ClusterRoleBindingList(ref), + "k8s.io/api/rbac/v1.ClusterRoleList": schema_k8sio_api_rbac_v1_ClusterRoleList(ref), + "k8s.io/api/rbac/v1.PolicyRule": schema_k8sio_api_rbac_v1_PolicyRule(ref), + "k8s.io/api/rbac/v1.Role": schema_k8sio_api_rbac_v1_Role(ref), + "k8s.io/api/rbac/v1.RoleBinding": schema_k8sio_api_rbac_v1_RoleBinding(ref), + "k8s.io/api/rbac/v1.RoleBindingList": schema_k8sio_api_rbac_v1_RoleBindingList(ref), + "k8s.io/api/rbac/v1.RoleList": schema_k8sio_api_rbac_v1_RoleList(ref), + "k8s.io/api/rbac/v1.RoleRef": schema_k8sio_api_rbac_v1_RoleRef(ref), + "k8s.io/api/rbac/v1.Subject": schema_k8sio_api_rbac_v1_Subject(ref), "k8s.io/apimachinery/pkg/api/resource.Quantity": schema_apimachinery_pkg_api_resource_Quantity(ref), "k8s.io/apimachinery/pkg/api/resource.int64Amount": schema_apimachinery_pkg_api_resource_int64Amount(ref), "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup": schema_pkg_apis_meta_v1_APIGroup(ref), @@ -2141,11 +2154,42 @@ func schema_pkg_apis_internalacornio_v1_Acorn(ref common.ReferenceCallback) comm }, }, }, + "roles": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.PolicyRule"), + }, + }, + }, + }, + }, + "clusterRoles": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.PolicyRule"), + }, + }, + }, + }, + }, + "permissions": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Permissions"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.AcornBuild", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.PortDef", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.SecretBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.ServiceBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.VolumeBinding"}, + "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.AcornBuild", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Permissions", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.PortDef", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.SecretBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.ServiceBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.VolumeBinding", "k8s.io/api/rbac/v1.PolicyRule"}, } } @@ -2549,11 +2593,16 @@ func schema_pkg_apis_internalacornio_v1_AppInstanceSpec(ref common.ReferenceCall }, }, }, + "permissions": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Permissions"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.EndpointBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.PortBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.SecretBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.ServiceBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.VolumeBinding"}, + "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.EndpointBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Permissions", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.PortBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.SecretBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.ServiceBinding", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.VolumeBinding"}, } } @@ -3108,6 +3157,11 @@ func schema_pkg_apis_internalacornio_v1_Container(ref common.ReferenceCallback) }, }, }, + "permissions": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Permissions"), + }, + }, "scale": { SchemaProps: spec.SchemaProps{ Description: "Scale is only available on containers, not sidecars or jobs", @@ -3155,7 +3209,7 @@ func schema_pkg_apis_internalacornio_v1_Container(ref common.ReferenceCallback) }, }, Dependencies: []string{ - "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Alias", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Build", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Container", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Dependency", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.EnvVar", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.File", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.PortDef", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Probe", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.VolumeMount"}, + "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Alias", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Build", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Container", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Dependency", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.EnvVar", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.File", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Permissions", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.PortDef", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.Probe", "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1.VolumeMount"}, } } @@ -3774,6 +3828,46 @@ func schema_pkg_apis_internalacornio_v1_ParamSpec(ref common.ReferenceCallback) } } +func schema_pkg_apis_internalacornio_v1_Permissions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "rules": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.PolicyRule"), + }, + }, + }, + }, + }, + "clusterRules": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.PolicyRule"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/rbac/v1.PolicyRule"}, + } +} + func schema_pkg_apis_internalacornio_v1_Platform(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -16043,6 +16137,642 @@ func schema_k8sio_api_core_v1_WindowsSecurityContextOptions(ref common.Reference } } +func schema_k8sio_api_rbac_v1_AggregationRule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clusterRoleSelectors": { + SchemaProps: spec.SchemaProps{ + Description: "ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules. If any of the selectors match, then the ClusterRole's permissions will be added", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + } +} + +func schema_k8sio_api_rbac_v1_ClusterRole(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterRole is a cluster level, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding or ClusterRoleBinding.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "rules": { + SchemaProps: spec.SchemaProps{ + Description: "Rules holds all the PolicyRules for this ClusterRole", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.PolicyRule"), + }, + }, + }, + }, + }, + "aggregationRule": { + SchemaProps: spec.SchemaProps{ + Description: "AggregationRule is an optional field that describes how to build the Rules for this ClusterRole. If AggregationRule is set, then the Rules are controller managed and direct changes to Rules will be stomped by the controller.", + Ref: ref("k8s.io/api/rbac/v1.AggregationRule"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/rbac/v1.AggregationRule", "k8s.io/api/rbac/v1.PolicyRule", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_rbac_v1_ClusterRoleBinding(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterRoleBinding references a ClusterRole, but not contain it. It can reference a ClusterRole in the global namespace, and adds who information via Subject.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "subjects": { + SchemaProps: spec.SchemaProps{ + Description: "Subjects holds references to the objects the role applies to.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.Subject"), + }, + }, + }, + }, + }, + "roleRef": { + SchemaProps: spec.SchemaProps{ + Description: "RoleRef can only reference a ClusterRole in the global namespace. If the RoleRef cannot be resolved, the Authorizer must return an error.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.RoleRef"), + }, + }, + }, + Required: []string{"roleRef"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/rbac/v1.RoleRef", "k8s.io/api/rbac/v1.Subject", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_rbac_v1_ClusterRoleBindingList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterRoleBindingList is a collection of ClusterRoleBindings", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is a list of ClusterRoleBindings", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.ClusterRoleBinding"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/rbac/v1.ClusterRoleBinding", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_rbac_v1_ClusterRoleList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterRoleList is a collection of ClusterRoles", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is a list of ClusterRoles", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.ClusterRole"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/rbac/v1.ClusterRole", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_rbac_v1_PolicyRule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PolicyRule holds information that describes a policy rule, but does not contain information about who the rule applies to or which namespace the rule applies to.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "verbs": { + SchemaProps: spec.SchemaProps{ + Description: "Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "apiGroups": { + SchemaProps: spec.SchemaProps{ + Description: "APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "Resources is a list of resources this rule applies to. '*' represents all resources.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "resourceNames": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "nonResourceURLs": { + SchemaProps: spec.SchemaProps{ + Description: "NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. Rules can either apply to API resources (such as \"pods\" or \"secrets\") or non-resource URL paths (such as \"/api\"), but not both.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"verbs"}, + }, + }, + } +} + +func schema_k8sio_api_rbac_v1_Role(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Role is a namespaced, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "rules": { + SchemaProps: spec.SchemaProps{ + Description: "Rules holds all the PolicyRules for this Role", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.PolicyRule"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/rbac/v1.PolicyRule", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_rbac_v1_RoleBinding(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleBinding references a role, but does not contain it. It can reference a Role in the same namespace or a ClusterRole in the global namespace. It adds who information via Subjects and namespace information by which namespace it exists in. RoleBindings in a given namespace only have effect in that namespace.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "subjects": { + SchemaProps: spec.SchemaProps{ + Description: "Subjects holds references to the objects the role applies to.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.Subject"), + }, + }, + }, + }, + }, + "roleRef": { + SchemaProps: spec.SchemaProps{ + Description: "RoleRef can reference a Role in the current namespace or a ClusterRole in the global namespace. If the RoleRef cannot be resolved, the Authorizer must return an error.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.RoleRef"), + }, + }, + }, + Required: []string{"roleRef"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/rbac/v1.RoleRef", "k8s.io/api/rbac/v1.Subject", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_rbac_v1_RoleBindingList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleBindingList is a collection of RoleBindings", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is a list of RoleBindings", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.RoleBinding"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/rbac/v1.RoleBinding", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_rbac_v1_RoleList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleList is a collection of Roles", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is a list of Roles", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/rbac/v1.Role"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/rbac/v1.Role", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_rbac_v1_RoleRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleRef contains information that points to the role being used", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiGroup": { + SchemaProps: spec.SchemaProps{ + Description: "APIGroup is the group for the resource being referenced", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is the type of resource being referenced", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of resource being referenced", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"apiGroup", "kind", "name"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_rbac_v1_Subject(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind of object being referenced. Values defined by this API group are \"User\", \"Group\", and \"ServiceAccount\". If the Authorizer does not recognized the kind value, the Authorizer should report an error.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "apiGroup": { + SchemaProps: spec.SchemaProps{ + Description: "APIGroup holds the API group of the referenced subject. Defaults to \"\" for ServiceAccount subjects. Defaults to \"rbac.authorization.k8s.io\" for User and Group subjects.", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the object being referenced.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace of the referenced object. If the object kind is non-namespace, such as \"User\" or \"Group\", and this value is not empty the Authorizer should report an error.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"kind", "name"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + func schema_apimachinery_pkg_api_resource_Quantity(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/rulerequest/handle.go b/pkg/rulerequest/handle.go new file mode 100644 index 000000000..ac37c0c58 --- /dev/null +++ b/pkg/rulerequest/handle.go @@ -0,0 +1,72 @@ +package rulerequest + +import ( + "context" + "errors" + "fmt" + + "github.com/AlecAivazis/survey/v2" + apiv1 "github.com/acorn-io/acorn/pkg/apis/api.acorn.io/v1" + v1 "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1" + "github.com/acorn-io/acorn/pkg/cli/builder/table" + "github.com/acorn-io/acorn/pkg/client" + "github.com/acorn-io/acorn/pkg/tables" + "github.com/pterm/pterm" +) + +func PromptRun(ctx context.Context, c client.Client, dangerous bool, image string, opts client.AppRunOptions) (*apiv1.App, error) { + app, err := c.AppRun(ctx, image, &opts) + if permErr := (*client.ErrRulesNeeded)(nil); errors.As(err, &permErr) { + if ok, promptErr := handleDangerous(dangerous, &permErr.Permissions); promptErr != nil { + return nil, fmt.Errorf("%s: %w", promptErr.Error(), err) + } else if ok { + opts.Permissions = &permErr.Permissions + app, err = c.AppRun(ctx, image, &opts) + } + } + return app, err +} + +func PromptUpdate(ctx context.Context, c client.Client, dangerous bool, name string, opts client.AppUpdateOptions) (*apiv1.App, error) { + app, err := c.AppUpdate(ctx, name, &opts) + if permErr := (*client.ErrRulesNeeded)(nil); errors.As(err, &permErr) { + if ok, promptErr := handleDangerous(dangerous, &permErr.Permissions); promptErr != nil { + return nil, fmt.Errorf("%s: %w", promptErr.Error(), err) + } else if ok { + opts.Permissions = &permErr.Permissions + app, err = c.AppUpdate(ctx, name, &opts) + } + } + return app, err +} + +func handleDangerous(dangerous bool, perms *v1.Permissions) (bool, error) { + if dangerous { + return true, nil + } + + pterm.Warning.Println( + `This application would like to request the following runtime permissions. +This could be VERY DANGEROUS to the cluster if you do not trust this +application. If you are unsure say no.`) + pterm.Println() + + requests := ToRuleRequests(perms) + writer := table.NewWriter(tables.RuleRequests, "", false, "") + for _, request := range requests { + writer.Write(request) + } + + if err := writer.Close(); err != nil { + return false, err + } + + pterm.Println() + + var shouldAllow = false + err := survey.AskOne(&survey.Confirm{ + Message: "Do you want to allow this app to have these (POTENTIALLY DANGEROUS) permissions?", + Default: false, + }, &shouldAllow) + return shouldAllow, err +} diff --git a/pkg/rulerequest/perms.go b/pkg/rulerequest/perms.go new file mode 100644 index 000000000..f20bd33c8 --- /dev/null +++ b/pkg/rulerequest/perms.go @@ -0,0 +1,68 @@ +package rulerequest + +import ( + "strings" + + v1 "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1" + rbacv1 "k8s.io/api/rbac/v1" +) + +type RuleRequest struct { + Scope string + Verbs string + Resource string +} + +func ToRuleRequests(perm *v1.Permissions) (result []RuleRequest) { + result = append(result, rulesToRequests(perm.ClusterRules, "cluster")...) + result = append(result, rulesToRequests(perm.Rules, "app")...) + return +} + +func rulesToRequests(rules []rbacv1.PolicyRule, scope string) (result []RuleRequest) { + for _, rule := range rules { + result = append(result, ruleToRequests(rule, scope)...) + } + return +} + +func ruleToRequests(rule rbacv1.PolicyRule, scope string) (result []RuleRequest) { + verbs := strings.Join(rule.Verbs, ",") + + if len(rule.NonResourceURLs) > 0 { + for _, url := range rule.NonResourceURLs { + result = append(result, RuleRequest{ + Scope: "cluster", + Resource: url, + Verbs: verbs, + }) + } + return + } + + for _, apiGroup := range rule.APIGroups { + for _, resource := range rule.Resources { + if apiGroup != "" { + resource += "." + apiGroup + } + + if len(rule.ResourceNames) == 0 { + result = append(result, RuleRequest{ + Scope: scope, + Resource: resource, + Verbs: verbs, + }) + } else { + for _, resourceName := range rule.ResourceNames { + result = append(result, RuleRequest{ + Scope: scope, + Resource: resource + "/" + resourceName, + Verbs: verbs, + }) + } + } + } + } + + return +} diff --git a/pkg/run/run.go b/pkg/run/run.go index c24d5ea1a..28715d3ab 100644 --- a/pkg/run/run.go +++ b/pkg/run/run.go @@ -166,6 +166,7 @@ type Options struct { DeployArgs map[string]interface{} DevMode *bool Client client.WithWatch + Permissions *v1.Permissions } func (o *Options) Complete() (*Options, error) { @@ -243,6 +244,7 @@ func Run(ctx context.Context, image string, opts *Options) (*v1.AppInstance, err PublishProtocols: opts.PublishProtocols, Profiles: opts.Profiles, DevMode: opts.DevMode, + Permissions: opts.Permissions, }, } diff --git a/pkg/scheme/scheme.go b/pkg/scheme/scheme.go index cc8074d5f..ba2c03c22 100644 --- a/pkg/scheme/scheme.go +++ b/pkg/scheme/scheme.go @@ -9,6 +9,7 @@ import ( batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -32,6 +33,7 @@ func AddToScheme(scheme *runtime.Scheme) error { errs = append(errs, networkingv1.AddToScheme(scheme)) errs = append(errs, storagev1.AddToScheme(scheme)) errs = append(errs, apiregistrationv1.AddToScheme(scheme)) + errs = append(errs, rbacv1.AddToScheme(scheme)) return merr.NewErrors(errs...) } diff --git a/pkg/server/registry/apps/apps.go b/pkg/server/registry/apps/apps.go index 40fd0deb0..fa85a8a28 100644 --- a/pkg/server/registry/apps/apps.go +++ b/pkg/server/registry/apps/apps.go @@ -24,19 +24,21 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -func NewStorage(c client.WithWatch, images *images.Storage) *Storage { +func NewStorage(c client.WithWatch, images *images.Storage, imageDetails *images.ImageDetails) *Storage { return &Storage{ TableConvertor: tables.AppConverter, client: c, images: images, + imageDetails: imageDetails, } } type Storage struct { rest.TableConvertor - client client.WithWatch - images *images.Storage + client client.WithWatch + images *images.Storage + imageDetails *images.ImageDetails } func (s *Storage) NewList() runtime.Object { @@ -161,6 +163,7 @@ func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidati DevMode: params.Spec.DevMode, PublishProtocols: params.Spec.PublishProtocols, Profiles: params.Spec.Profiles, + Permissions: params.Spec.Permissions, } ) @@ -169,6 +172,15 @@ func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidati return nil, err } + perms, err := s.getPermissions(ctx, image) + if err != nil { + return nil, err + } + + if err := s.compareAndCheckPermissions(ctx, perms, runOpts.Permissions); err != nil { + return nil, err + } + app, err = run.Run(ctx, image, &runOpts) if err != nil { return nil, err @@ -217,6 +229,15 @@ func (s *Storage) Update(ctx context.Context, name string, objInfo rest.UpdatedO updatedAppInstance.Name = oldAppInstance.Name updatedAppInstance.Namespace = oldAppInstance.Namespace + perms, err := s.getPermissions(ctx, updatedAppInstance.Spec.Image) + if err != nil { + return nil, false, err + } + + if err := s.compareAndCheckPermissions(ctx, perms, updatedAppInstance.Spec.Permissions); err != nil { + return nil, false, err + } + if err := s.client.Update(ctx, updatedAppInstance); err != nil { return nil, false, err } diff --git a/pkg/server/registry/apps/image.go b/pkg/server/registry/apps/image.go index 133760d93..c32494b90 100644 --- a/pkg/server/registry/apps/image.go +++ b/pkg/server/registry/apps/image.go @@ -2,14 +2,23 @@ package apps import ( "context" + "errors" "fmt" "strings" + v1 "github.com/acorn-io/acorn/pkg/apis/internal.acorn.io/v1" + "github.com/acorn-io/acorn/pkg/client" "github.com/acorn-io/acorn/pkg/pullsecret" "github.com/acorn-io/acorn/pkg/tags" + "github.com/acorn-io/baaah/pkg/merr" + "github.com/acorn-io/baaah/pkg/typed" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" + authv1 "k8s.io/api/authorization/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/equality" apierror "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apiserver/pkg/endpoints/request" ) func (s *Storage) checkRemotePermissions(ctx context.Context, namespace, image string) error { @@ -30,6 +39,177 @@ func (s *Storage) checkRemotePermissions(ctx context.Context, namespace, image s return nil } +func (s *Storage) check(ctx context.Context, sar *authv1.SubjectAccessReview, rule rbacv1.PolicyRule) error { + err := s.client.Create(ctx, sar) + if err != nil { + return err + } + if !sar.Status.Allowed { + return &client.ErrNotAuthorized{ + Rule: rule, + } + } + return nil +} + +func (s *Storage) checkNonResourceRole(ctx context.Context, sar *authv1.SubjectAccessReview, rule rbacv1.PolicyRule, namespace string) error { + if len(rule.Verbs) == 0 { + return fmt.Errorf("can not deploy acorn due to requesting role with empty verbs") + } + + for _, url := range rule.NonResourceURLs { + for _, verb := range rule.Verbs { + sar := sar.DeepCopy() + sar.Spec.NonResourceAttributes = &authv1.NonResourceAttributes{ + Path: url, + Verb: verb, + } + if err := s.check(ctx, sar, rule); err != nil { + return err + } + } + } + + return nil +} + +func (s *Storage) checkResourceRole(ctx context.Context, sar *authv1.SubjectAccessReview, rule rbacv1.PolicyRule, namespace string) error { + if len(rule.APIGroups) == 0 { + return fmt.Errorf("can not deploy acorn due to requesting role with empty apiGroups") + } + if len(rule.Verbs) == 0 { + return fmt.Errorf("can not deploy acorn due to requesting role with empty verbs") + } + if len(rule.Resources) == 0 { + return fmt.Errorf("can not deploy acorn due to requesting role with empty resources") + } + for _, verb := range rule.Verbs { + for _, apiGroup := range rule.APIGroups { + for _, resource := range rule.Resources { + resource, subResource, _ := strings.Cut(resource, "/") + if len(rule.ResourceNames) == 0 { + sar := sar.DeepCopy() + sar.Spec.ResourceAttributes = &authv1.ResourceAttributes{ + Namespace: namespace, + Verb: verb, + Group: apiGroup, + Version: "*", + Resource: resource, + Subresource: subResource, + } + if err := s.check(ctx, sar, rule); err != nil { + return err + } + } else { + for _, resourceName := range rule.ResourceNames { + sar := sar.DeepCopy() + sar.Spec.ResourceAttributes = &authv1.ResourceAttributes{ + Namespace: namespace, + Verb: verb, + Group: apiGroup, + Version: "*", + Resource: resource, + Subresource: subResource, + Name: resourceName, + } + if err := s.check(ctx, sar, rule); err != nil { + return err + } + } + } + } + } + } + + return nil +} + +func (s *Storage) checkRules(ctx context.Context, sar *authv1.SubjectAccessReview, rules []rbacv1.PolicyRule, namespace string) error { + var errs []error + for _, rule := range rules { + if len(rule.NonResourceURLs) > 0 { + if err := s.checkNonResourceRole(ctx, sar, rule, namespace); err != nil { + errs = append(errs, err) + } + } else { + if err := s.checkResourceRole(ctx, sar, rule, namespace); err != nil { + errs = append(errs, err) + } + } + } + return merr.NewErrors(errs...) +} + +func (s *Storage) compareAndCheckPermissions(ctx context.Context, perms v1.Permissions, requestedPerms *v1.Permissions) error { + if len(perms.ClusterRules) == 0 && len(perms.Rules) == 0 { + return nil + } + + if !equality.Semantic.DeepEqual(perms.ClusterRules, requestedPerms.Get().ClusterRules) || + !equality.Semantic.DeepEqual(perms.Rules, requestedPerms.Get().Rules) { + return &client.ErrRulesNeeded{ + Permissions: perms, + } + } + + user, ok := request.UserFrom(ctx) + if !ok { + return fmt.Errorf("failed to find active user to check current privileges") + } + + sar := &authv1.SubjectAccessReview{ + Spec: authv1.SubjectAccessReviewSpec{ + User: user.GetName(), + Groups: user.GetGroups(), + Extra: map[string]authv1.ExtraValue{}, + UID: user.GetUID(), + }, + } + + for k, v := range user.GetExtra() { + sar.Spec.Extra[k] = v + } + + var errs []error + if err := s.checkRules(ctx, sar, perms.ClusterRules, ""); err != nil { + errs = append(errs, err) + } + + ns, _ := request.NamespaceFrom(ctx) + if err := s.checkRules(ctx, sar, perms.Rules, ns); err != nil { + errs = append(errs, err) + } + + return merr.NewErrors(errs...) +} + +func (s *Storage) getPermissions(ctx context.Context, image string) (result v1.Permissions, _ error) { + details, err := s.imageDetails.GetDetails(ctx, image, nil, nil) + if err != nil { + return result, err + } + + if details.ParseError != "" { + return result, errors.New(details.ParseError) + } + + for _, entry := range typed.Sorted(details.AppSpec.Containers) { + result.ClusterRules = append(result.ClusterRules, entry.Value.Permissions.Get().ClusterRules...) + result.Rules = append(result.Rules, entry.Value.Permissions.Get().Rules...) + for _, sidecar := range typed.Sorted(entry.Value.Sidecars) { + result.ClusterRules = append(result.ClusterRules, sidecar.Value.Permissions.Get().ClusterRules...) + result.Rules = append(result.Rules, sidecar.Value.Permissions.Get().Rules...) + } + } + + for _, entry := range typed.Sorted(details.AppSpec.Acorns) { + result.ClusterRules = append(result.ClusterRules, entry.Value.Permissions.Get().ClusterRules...) + result.Rules = append(result.Rules, entry.Value.Permissions.Get().Rules...) + } + + return result, nil +} + func (s *Storage) resolveTag(ctx context.Context, namespace, image string) (string, error) { localImage, err := s.images.ImageGet(ctx, image) if apierror.IsNotFound(err) { diff --git a/pkg/server/registry/images/detail.go b/pkg/server/registry/images/detail.go index 9defad2bd..a094a9b93 100644 --- a/pkg/server/registry/images/detail.go +++ b/pkg/server/registry/images/detail.go @@ -50,15 +50,15 @@ func (s *ImageDetails) Create(ctx context.Context, obj runtime.Object, createVal details.Name = ri.Name } } - return s.get(ctx, details.Name, details.Profiles, details.DeployArgs) + return s.GetDetails(ctx, details.Name, details.Profiles, details.DeployArgs) } func (s *ImageDetails) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { name = strings.ReplaceAll(name, "+", "/") - return s.get(ctx, name, nil, nil) + return s.GetDetails(ctx, name, nil, nil) } -func (s *ImageDetails) get(ctx context.Context, name string, profiles []string, deployArgs map[string]interface{}) (runtime.Object, error) { +func (s *ImageDetails) GetDetails(ctx context.Context, name string, profiles []string, deployArgs map[string]interface{}) (*apiv1.ImageDetails, error) { ns, _ := request.NamespaceFrom(ctx) imageName := name diff --git a/pkg/server/registry/registry.go b/pkg/server/registry/registry.go index 47b469ac8..c3e067398 100644 --- a/pkg/server/registry/registry.go +++ b/pkg/server/registry/registry.go @@ -21,6 +21,7 @@ import ( func APIStores(c client.WithWatch, cfg *clientgo.Config) (map[string]rest.Storage, error) { buildersStorage := builders.NewStorage(c) imagesStorage := images.NewStorage(c) + imagesDetails := images.NewImageDetails(c, imagesStorage) containerStorage := containers.NewStorage(c) tagsStorage := images.NewTagStorage(c, imagesStorage) containerExec, err := containers.NewContainerExec(c, containerStorage, cfg) @@ -37,7 +38,7 @@ func APIStores(c client.WithWatch, cfg *clientgo.Config) (map[string]rest.Storag return nil, err } - appsStorage := apps.NewStorage(c, imagesStorage) + appsStorage := apps.NewStorage(c, imagesStorage, imagesDetails) logsStorage, err := apps.NewLogs(c, appsStorage, cfg) if err != nil { return nil, err @@ -53,7 +54,7 @@ func APIStores(c client.WithWatch, cfg *clientgo.Config) (map[string]rest.Storag "images/tag": tagsStorage, "images/push": images.NewImagePush(c, imagesStorage), "images/pull": images.NewImagePull(c, imagesStorage, tagsStorage), - "images/details": images.NewImageDetails(c, imagesStorage), + "images/details": imagesDetails, "volumes": volumes.NewStorage(c), "containerreplicas": containerStorage, "containerreplicas/exec": containerExec, diff --git a/pkg/tables/tables.go b/pkg/tables/tables.go index cadeabf8b..4e46d4d78 100644 --- a/pkg/tables/tables.go +++ b/pkg/tables/tables.go @@ -61,4 +61,10 @@ var ( {"Controller-Image", "ControllerImage"}, } InfoConverter = MustConverter(Info) + + RuleRequests = [][]string{ + {"Verbs", "Verbs"}, + {"Resource", "Resource"}, + {"Scope", "Scope"}, + } ) diff --git a/schema/v1/app.cue b/schema/v1/app.cue index 8c5c12bf1..7d3f8e473 100644 --- a/schema/v1/app.cue +++ b/schema/v1/app.cue @@ -56,6 +56,10 @@ package v1 expose: #Port | *[...#Port] [=~"probes|probe"]: #Probes [=~"depends[oO]n|depends_on"]: string | *[...string] + permissions: { + rules: [...#RuleSpec] + clusterRules: [...#RuleSpec] + } } #ShortVolumeRef: =~"^[a-z][-a-z0-9]*$" @@ -173,6 +177,10 @@ package v1 secrets: [...string] links: [...string] deployArgs: [string]: _ + permissions: { + rules: [...#RuleSpec] + clusterRules: [...#RuleSpec] + } } #App: { diff --git a/schema/v1/spec.cue b/schema/v1/spec.cue index 75d3b8f90..bcaa2eebb 100644 --- a/schema/v1/spec.cue +++ b/schema/v1/spec.cue @@ -81,6 +81,18 @@ package v1 dirs: [string]: #VolumeMountSpec probes: [...#ProbeSpec] dependencies: [...#DependencySpec] + permissions: { + rules: [...#RuleSpec] + clusterRules: [...#RuleSpec] + } +} + +#RuleSpec: { + verbs: [...string] + apiGroups: [...string] + resources: [...string] + resourceNames: [...string] + nonResourceURLs: [...string] } #DependencySpec: { @@ -160,6 +172,10 @@ package v1 secrets: [...#SecretBinding] services: [...#ServiceBinding] deployArgs: [string]: _ + permissions: { + rules: [...#RuleSpec] + clusterRules: [...#RuleSpec] + } } #AppSpec: { diff --git a/schema/v1/transform/normalize/normalize.cue b/schema/v1/transform/normalize/normalize.cue index 2b09de43b..557d0a863 100644 --- a/schema/v1/transform/normalize/normalize.cue +++ b/schema/v1/transform/normalize/normalize.cue @@ -408,6 +408,8 @@ import ( dependencies: [ for y in IN.container[x] {targetName: y}] } } + + permissions: IN.container.permissions } } @@ -482,6 +484,7 @@ import ( }] } } + permissions: IN.permissions } }