From 7dddae5f1af0b85315af347292e287e2d5acdac2 Mon Sep 17 00:00:00 2001 From: Leonardo Luz Almeida Date: Thu, 1 Aug 2024 09:29:26 -0400 Subject: [PATCH] feat: Implement AppProject role management Signed-off-by: Leonardo Luz Almeida --- PROJECT | 1 + api/argoproj/v1alpha1/appproject.go | 55 +++++++ api/argoproj/v1alpha1/groupversion_info.go | 37 +++++ .../v1alpha1/zz_generated.deepcopy.go | 150 ++++++++++++++++++ cmd/main.go | 3 +- config/samples/argoproj.io_appproject.yaml | 0 ...hemeral-access_v1alpha1_accessrequest.yaml | 11 +- config/samples/kustomization.yaml | 1 + .../controller/accessrequest_controller.go | 2 +- test/manifests/accessrequest.yaml | 13 -- 10 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 api/argoproj/v1alpha1/appproject.go create mode 100644 api/argoproj/v1alpha1/groupversion_info.go create mode 100644 api/argoproj/v1alpha1/zz_generated.deepcopy.go create mode 100644 config/samples/argoproj.io_appproject.yaml delete mode 100644 test/manifests/accessrequest.yaml diff --git a/PROJECT b/PROJECT index dfe71bd..6423147 100644 --- a/PROJECT +++ b/PROJECT @@ -5,6 +5,7 @@ domain: argoproj-labs.io layout: - go.kubebuilder.io/v4 +multigroup: true projectName: argocd-ephemeral-access repo: github.com/argoproj-labs/ephemeral-access resources: diff --git a/api/argoproj/v1alpha1/appproject.go b/api/argoproj/v1alpha1/appproject.go new file mode 100644 index 0000000..56f13ce --- /dev/null +++ b/api/argoproj/v1alpha1/appproject.go @@ -0,0 +1,55 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AppProject provides a logical grouping of applications, providing controls for: +// * who can access these applications (roles, OIDC group claims bindings) +// * and what they can do (RBAC policies) +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=appprojects,shortName=appproj;appprojs +type AppProject struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + Spec AppProjectSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` +} + +// AppProjectSpec is the specification of an AppProject +type AppProjectSpec struct { + // Roles are user defined RBAC roles associated with this project + Roles []ProjectRole `json:"roles,omitempty" protobuf:"bytes,1,rep,name=roles"` +} + +// ProjectRole represents a role that has access to a project +type ProjectRole struct { + // Name is a name for this role + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + // Description is a description of the role + Description string `json:"description,omitempty" protobuf:"bytes,2,opt,name=description"` + // Policies Stores a list of casbin formatted strings that define access policies for the role in the project + Policies []string `json:"policies,omitempty" protobuf:"bytes,3,rep,name=policies"` + // JWTTokens are a list of generated JWT tokens bound to this role + JWTTokens []JWTToken `json:"jwtTokens,omitempty" protobuf:"bytes,4,rep,name=jwtTokens"` + // Groups are a list of OIDC group claims bound to this role + Groups []string `json:"groups,omitempty" protobuf:"bytes,5,rep,name=groups"` +} + +// JWTToken holds the issuedAt and expiresAt values of a token +type JWTToken struct { + IssuedAt int64 `json:"iat" protobuf:"int64,1,opt,name=iat"` + ExpiresAt int64 `json:"exp,omitempty" protobuf:"int64,2,opt,name=exp"` + ID string `json:"id,omitempty" protobuf:"bytes,3,opt,name=id"` +} + +// AccessRequestList contains a list of AccessRequest +// +kubebuilder:object:root=true +type AppProjectList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AppProject `json:"items"` +} + +func init() { + SchemeBuilder.Register(&AppProject{}, &AppProjectList{}) +} diff --git a/api/argoproj/v1alpha1/groupversion_info.go b/api/argoproj/v1alpha1/groupversion_info.go new file mode 100644 index 0000000..91fb0ef --- /dev/null +++ b/api/argoproj/v1alpha1/groupversion_info.go @@ -0,0 +1,37 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1alpha1 contains API Schema definitions for the Argo CD AppProject resource +// +kubebuilder:skip +// +kubebuilder:object:generate=true +// +groupName=argoproj.io +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "argoproj.io", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/argoproj/v1alpha1/zz_generated.deepcopy.go b/api/argoproj/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..3d4b4cf --- /dev/null +++ b/api/argoproj/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppProject) DeepCopyInto(out *AppProject) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProject. +func (in *AppProject) DeepCopy() *AppProject { + if in == nil { + return nil + } + out := new(AppProject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AppProject) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppProjectList) DeepCopyInto(out *AppProjectList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AppProject, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProjectList. +func (in *AppProjectList) DeepCopy() *AppProjectList { + if in == nil { + return nil + } + out := new(AppProjectList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AppProjectList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppProjectSpec) DeepCopyInto(out *AppProjectSpec) { + *out = *in + if in.Roles != nil { + in, out := &in.Roles, &out.Roles + *out = make([]ProjectRole, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProjectSpec. +func (in *AppProjectSpec) DeepCopy() *AppProjectSpec { + if in == nil { + return nil + } + out := new(AppProjectSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTToken) DeepCopyInto(out *JWTToken) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTToken. +func (in *JWTToken) DeepCopy() *JWTToken { + if in == nil { + return nil + } + out := new(JWTToken) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProjectRole) DeepCopyInto(out *ProjectRole) { + *out = *in + if in.Policies != nil { + in, out := &in.Policies, &out.Policies + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.JWTTokens != nil { + in, out := &in.JWTTokens, &out.JWTTokens + *out = make([]JWTToken, len(*in)) + copy(*out, *in) + } + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectRole. +func (in *ProjectRole) DeepCopy() *ProjectRole { + if in == nil { + return nil + } + out := new(ProjectRole) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/main.go b/cmd/main.go index 3c4e02c..fb598a6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -35,6 +35,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + appprojectv1alpha1 "github.com/argoproj-labs/ephemeral-access/api/argoproj/v1alpha1" ephemeralaccessv1alpha1 "github.com/argoproj-labs/ephemeral-access/api/v1alpha1" "github.com/argoproj-labs/ephemeral-access/internal/controller" // +kubebuilder:scaffold:imports @@ -47,8 +48,8 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(ephemeralaccessv1alpha1.AddToScheme(scheme)) + utilruntime.Must(appprojectv1alpha1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } diff --git a/config/samples/argoproj.io_appproject.yaml b/config/samples/argoproj.io_appproject.yaml new file mode 100644 index 0000000..e69de29 diff --git a/config/samples/ephemeral-access_v1alpha1_accessrequest.yaml b/config/samples/ephemeral-access_v1alpha1_accessrequest.yaml index 5fd302b..b7e6e4d 100644 --- a/config/samples/ephemeral-access_v1alpha1_accessrequest.yaml +++ b/config/samples/ephemeral-access_v1alpha1_accessrequest.yaml @@ -4,6 +4,13 @@ metadata: labels: app.kubernetes.io/name: argocd-ephemeral-access app.kubernetes.io/managed-by: kustomize - name: accessrequest-sample + name: some-application-username + namespace: ephemeral spec: - # TODO(user): Add fields here + duration: '1m' + targetRoleName: ephemeral-write-access + appProject: + name: some-argocd-appproject + namespace: some-namespace + subjects: + - username: some_user@fakedomain.com diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index d44e835..9dc2ffa 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,4 +1,5 @@ ## Append samples of your project ## resources: - ephemeral-access_v1alpha1_accessrequest.yaml +- argoproj.io_appproject.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/internal/controller/accessrequest_controller.go b/internal/controller/accessrequest_controller.go index 4726285..9b0ad44 100644 --- a/internal/controller/accessrequest_controller.go +++ b/internal/controller/accessrequest_controller.go @@ -28,7 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - argocd "github.com/argoproj-labs/ephemeral-access/api/appproject/v1alpha1" + argocd "github.com/argoproj-labs/ephemeral-access/api/argoproj/v1alpha1" api "github.com/argoproj-labs/ephemeral-access/api/v1alpha1" "github.com/argoproj-labs/ephemeral-access/internal/log" ) diff --git a/test/manifests/accessrequest.yaml b/test/manifests/accessrequest.yaml deleted file mode 100644 index 208856f..0000000 --- a/test/manifests/accessrequest.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: ephemeral-access.argoproj-labs.io/v1alpha1 -kind: AccessRequest -metadata: - name: some-application-username - namespace: ephemeral -spec: - duration: '1m' - targetRoleName: ephemeral-write-access - appProject: - name: some-argocd-appproject - namespace: some-namespace - subjects: - - username: some_user@fakedomain.com