diff --git a/Gopkg.lock b/Gopkg.lock index 782e78f6a27c0..58c86ccec3520 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -53,6 +53,14 @@ revision = "ac241c95c13f08e868cd6f5ee32c9ce273e239ff" version = "v2.1.1" +[[projects]] + branch = "master" + digest = "1:0667628f85b38f0422880ad3a503b1cf139bca5a817d29cd4e7ffccd5914869c" + name = "github.com/argoproj/pkg" + packages = ["time"] + pruneopts = "" + revision = "881057947d921c5d62af84ad15cd3c6fb36d6077" + [[projects]] digest = "1:d8a2bb36a048d1571bcc1aee208b61f39dc16c6c53823feffd37449dde162507" name = "github.com/asaskevich/govalidator" @@ -127,6 +135,14 @@ revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" version = "v3.2.0" +[[projects]] + branch = "master" + digest = "1:f1a75a8e00244e5ea77ff274baa9559eb877437b240ee7b278f3fc560d9f08bf" + name = "github.com/dustin/go-humanize" + packages = ["."] + pruneopts = "" + revision = "9f541cc9db5d55bce703bd99987c9d5cb8eea45e" + [[projects]] digest = "1:971e9ba63a417c5f1f83ab358677bc59e96ff04285f26c6646ff089fb60b15e8" name = "github.com/emicklei/go-restful" @@ -1216,11 +1232,13 @@ analyzer-version = 1 input-imports = [ "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1", + "github.com/argoproj/pkg/time", "github.com/casbin/casbin", "github.com/casbin/casbin/model", "github.com/coreos/dex/api", "github.com/coreos/go-oidc", "github.com/dgrijalva/jwt-go", + "github.com/dustin/go-humanize", "github.com/ghodss/yaml", "github.com/go-openapi/loads", "github.com/go-openapi/runtime/middleware", diff --git a/Gopkg.toml b/Gopkg.toml index 035ae78078ec7..fd823280b247c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -7,6 +7,8 @@ required = [ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger", "github.com/golang/protobuf/protoc-gen-go", "golang.org/x/tools/cmd/cover", + "github.com/argoproj/pkg/time", + "github.com/dustin/go-humanize", ] [[constraint]] diff --git a/Procfile b/Procfile index 78946f4c275b6..17370eee6e7bf 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,4 @@ controller: go run ./cmd/argocd-application-controller/main.go -api-server: go run ./cmd/argocd-server/main.go --insecure --disable-auth +api-server: go run ./cmd/argocd-server/main.go --insecure repo-server: go run ./cmd/argocd-repo-server/main.go --loglevel debug dex: sh -c "go run ./cmd/argocd-util/main.go gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p 5556:5556 -p 5557:5557 -v `pwd`/dist/dex.yaml:/dex.yaml quay.io/coreos/dex:v2.10.0 serve /dex.yaml" diff --git a/cmd/argocd/commands/project.go b/cmd/argocd/commands/project.go index 0285322804f46..49a31df420cad 100644 --- a/cmd/argocd/commands/project.go +++ b/cmd/argocd/commands/project.go @@ -1,17 +1,20 @@ package commands import ( + "context" + "fmt" "os" + "strconv" + "strings" + "text/tabwriter" + "time" + timeutil "github.com/argoproj/pkg/time" + "github.com/dustin/go-humanize" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - - "strings" - - "context" - - "fmt" - "text/tabwriter" + "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/argoproj/argo-cd/errors" argocdclient "github.com/argoproj/argo-cd/pkg/apiclient" @@ -19,8 +22,11 @@ import ( "github.com/argoproj/argo-cd/server/project" "github.com/argoproj/argo-cd/util" "github.com/argoproj/argo-cd/util/git" - "github.com/spf13/pflag" - "k8s.io/apimachinery/pkg/apis/meta/v1" + projectutil "github.com/argoproj/argo-cd/util/project" +) + +const ( + policyTemplate = "p, proj:%s:%s, applications, %s, %s/%s, %s" ) type projectOpts struct { @@ -29,6 +35,12 @@ type projectOpts struct { sources []string } +type policyOpts struct { + action string + permission string + object string +} + func (opts *projectOpts) GetDestinations() []v1alpha1.ApplicationDestination { destinations := make([]v1alpha1.ApplicationDestination, 0) for _, destStr := range opts.destinations { @@ -55,6 +67,7 @@ func NewProjectCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { os.Exit(1) }, } + command.AddCommand(NewProjectRoleCommand(clientOpts)) command.AddCommand(NewProjectCreateCommand(clientOpts)) command.AddCommand(NewProjectDeleteCommand(clientOpts)) command.AddCommand(NewProjectListCommand(clientOpts)) @@ -73,6 +86,336 @@ func addProjFlags(command *cobra.Command, opts *projectOpts) { command.Flags().StringArrayVarP(&opts.sources, "src", "s", []string{}, "Allowed deployment source repository URL.") } +func addPolicyFlags(command *cobra.Command, opts *policyOpts) { + command.Flags().StringVarP(&opts.action, "action", "a", "", "Action to grant/deny permission on") + command.Flags().StringVarP(&opts.permission, "permission", "p", "allow", "Whether to allow or deny access to object with the action. This can only be 'allow' or 'deny'") + command.Flags().StringVarP(&opts.object, "object", "o", "", "Object within the project to grant/deny access. Use '*' for a wildcard. Will want access to '/'") +} + +// NewProjectRoleCommand returns a new instance of the `argocd proj role` command +func NewProjectRoleCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + roleCommand := &cobra.Command{ + Use: "role", + Short: "Manage a project's role", + Run: func(c *cobra.Command, args []string) { + c.HelpFunc()(c, args) + os.Exit(1) + }, + } + roleCommand.AddCommand(NewProjectRoleListCommand(clientOpts)) + roleCommand.AddCommand(NewProjectRoleGetCommand(clientOpts)) + roleCommand.AddCommand(NewProjectRoleCreateCommand(clientOpts)) + roleCommand.AddCommand(NewProjectRoleDeleteCommand(clientOpts)) + roleCommand.AddCommand(NewProjectRoleCreateTokenCommand(clientOpts)) + roleCommand.AddCommand(NewProjectRoleDeleteTokenCommand(clientOpts)) + roleCommand.AddCommand(NewProjectRoleAddPolicyCommand(clientOpts)) + roleCommand.AddCommand(NewProjectRoleRemovePolicyCommand(clientOpts)) + return roleCommand +} + +// NewProjectRoleAddPolicyCommand returns a new instance of an `argocd proj role add-policy` command +func NewProjectRoleAddPolicyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var ( + opts policyOpts + ) + var command = &cobra.Command{ + Use: "add-policy PROJECT ROLE-NAME", + Short: "Add a policy to a project role", + Run: func(c *cobra.Command, args []string) { + if len(args) != 2 { + c.HelpFunc()(c, args) + os.Exit(1) + } + if len(opts.action) <= 0 { + log.Fatal("Action needs to longer than 0 characters") + } + if len(opts.object) <= 0 { + log.Fatal("Objects needs to longer than 0 characters") + + } + + projName := args[0] + roleName := args[1] + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer util.Close(conn) + + proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName}) + errors.CheckError(err) + + roleIndex, err := projectutil.GetRoleIndexByName(proj, roleName) + if err != nil { + log.Fatal(err) + } + role := proj.Spec.Roles[roleIndex] + + policy := fmt.Sprintf(policyTemplate, proj.Name, role.Name, opts.action, proj.Name, opts.object, opts.permission) + proj.Spec.Roles[roleIndex].Policies = append(role.Policies, policy) + + _, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj}) + errors.CheckError(err) + }, + } + addPolicyFlags(command, &opts) + return command +} + +// NewProjectRoleRemovePolicyCommand returns a new instance of an `argocd proj role remove-policy` command +func NewProjectRoleRemovePolicyCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var ( + opts policyOpts + ) + var command = &cobra.Command{ + Use: "remove-policy PROJECT ROLE-NAME", + Short: "Remove a policy from a role within a project", + Run: func(c *cobra.Command, args []string) { + if len(args) != 2 { + c.HelpFunc()(c, args) + os.Exit(1) + } + if opts.permission != "allow" && opts.permission != "deny" { + log.Fatal("Permission flag can only have the values 'allow' or 'deny'") + } + + if len(opts.action) <= 0 { + log.Fatal("Action needs to longer than 0 characters") + } + if len(opts.object) <= 0 { + log.Fatal("Objects needs to longer than 0 characters") + + } + + projName := args[0] + roleName := args[1] + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer util.Close(conn) + + proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName}) + errors.CheckError(err) + + roleIndex, err := projectutil.GetRoleIndexByName(proj, roleName) + if err != nil { + log.Fatal(err) + } + role := proj.Spec.Roles[roleIndex] + + policyToRemove := fmt.Sprintf(policyTemplate, proj.Name, role.Name, opts.action, proj.Name, opts.object, opts.permission) + duplicateIndex := -1 + for i, policy := range role.Policies { + if policy == policyToRemove { + duplicateIndex = i + break + } + } + if duplicateIndex < 0 { + return + } + role.Policies[duplicateIndex] = role.Policies[len(role.Policies)-1] + proj.Spec.Roles[roleIndex].Policies = role.Policies[:len(role.Policies)-1] + _, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj}) + errors.CheckError(err) + }, + } + addPolicyFlags(command, &opts) + return command +} + +// NewProjectRoleCreateCommand returns a new instance of an `argocd proj role create` command +func NewProjectRoleCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var ( + description string + ) + var command = &cobra.Command{ + Use: "create PROJECT ROLE-NAME", + Short: "Create a project role", + Run: func(c *cobra.Command, args []string) { + if len(args) != 2 { + c.HelpFunc()(c, args) + os.Exit(1) + } + projName := args[0] + roleName := args[1] + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer util.Close(conn) + + proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName}) + errors.CheckError(err) + + _, err = projectutil.GetRoleIndexByName(proj, roleName) + if err == nil { + return + } + proj.Spec.Roles = append(proj.Spec.Roles, v1alpha1.ProjectRole{Name: roleName, Description: description}) + + _, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj}) + errors.CheckError(err) + }, + } + command.Flags().StringVarP(&description, "description", "", "desc", "Project description") + return command +} + +// NewProjectRoleDeleteCommand returns a new instance of an `argocd proj role delete` command +func NewProjectRoleDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var command = &cobra.Command{ + Use: "delete PROJECT ROLE-NAME", + Short: "Delete a project role", + Run: func(c *cobra.Command, args []string) { + if len(args) != 2 { + c.HelpFunc()(c, args) + os.Exit(1) + } + projName := args[0] + roleName := args[1] + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer util.Close(conn) + + proj, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName}) + errors.CheckError(err) + + index, err := projectutil.GetRoleIndexByName(proj, roleName) + if err != nil { + return + } + proj.Spec.Roles[index] = proj.Spec.Roles[len(proj.Spec.Roles)-1] + proj.Spec.Roles = proj.Spec.Roles[:len(proj.Spec.Roles)-1] + + _, err = projIf.Update(context.Background(), &project.ProjectUpdateRequest{Project: proj}) + errors.CheckError(err) + }, + } + return command +} + +// NewProjectRoleCreateTokenCommand returns a new instance of an `argocd proj role create-token` command +func NewProjectRoleCreateTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var ( + expiresIn string + ) + var command = &cobra.Command{ + Use: "create-token PROJECT TOKEN-NAME", + Short: "Create a project token", + Run: func(c *cobra.Command, args []string) { + if len(args) != 2 { + c.HelpFunc()(c, args) + os.Exit(1) + } + projName := args[0] + roleName := args[1] + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer util.Close(conn) + duration, err := timeutil.ParseDuration(expiresIn) + errors.CheckError(err) + token, err := projIf.CreateToken(context.Background(), &project.ProjectTokenCreateRequest{Project: projName, Role: roleName, ExpiresIn: int64(duration.Seconds())}) + errors.CheckError(err) + fmt.Print(token.Token) + }, + } + command.Flags().StringVarP(&expiresIn, "expires-in", "e", "0s", "Duration before the token will expire. (Default: No expiration)") + + return command +} + +// NewProjectRoleDeleteTokenCommand returns a new instance of an `argocd proj role delete-token` command +func NewProjectRoleDeleteTokenCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var command = &cobra.Command{ + Use: "delete-token PROJECT ROLE-NAME ISSUED-AT", + Short: "Delete a project token", + Run: func(c *cobra.Command, args []string) { + if len(args) != 3 { + c.HelpFunc()(c, args) + os.Exit(1) + } + projName := args[0] + roleName := args[1] + issuedAt, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + log.Fatal(err) + } + + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer util.Close(conn) + + _, err = projIf.DeleteToken(context.Background(), &project.ProjectTokenDeleteRequest{Project: projName, Role: roleName, Iat: issuedAt}) + errors.CheckError(err) + }, + } + return command +} + +// NewProjectRoleListCommand returns a new instance of an `argocd proj roles list` command +func NewProjectRoleListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var command = &cobra.Command{ + Use: "list PROJECT", + Short: "List all the roles in a project", + Run: func(c *cobra.Command, args []string) { + if len(args) != 1 { + c.HelpFunc()(c, args) + os.Exit(1) + } + projName := args[0] + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer util.Close(conn) + + project, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName}) + errors.CheckError(err) + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "ROLE-NAME\tDESCRIPTION\n") + for _, role := range project.Spec.Roles { + fmt.Fprintf(w, "%s\t%s\n", role.Name, role.Description) + } + _ = w.Flush() + }, + } + return command +} + +// NewProjectRoleGetCommand returns a new instance of an `argocd proj roles get` command +func NewProjectRoleGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { + var command = &cobra.Command{ + Use: "get PROJECT ROLE-NAME", + Short: "Get the details of a specific role", + Run: func(c *cobra.Command, args []string) { + if len(args) != 2 { + c.HelpFunc()(c, args) + os.Exit(1) + } + projName := args[0] + roleName := args[1] + conn, projIf := argocdclient.NewClientOrDie(clientOpts).NewProjectClientOrDie() + defer util.Close(conn) + + project, err := projIf.Get(context.Background(), &project.ProjectQuery{Name: projName}) + errors.CheckError(err) + + index, err := projectutil.GetRoleIndexByName(project, roleName) + errors.CheckError(err) + role := project.Spec.Roles[index] + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "Role Name: %s\n", roleName) + fmt.Fprintf(w, "Description:%s\n", role.Description) + fmt.Fprintf(w, "Policies:\n") + fmt.Fprintf(w, "%s\n", project.ProjectPoliciesString()) + fmt.Fprintf(w, "Jwt Tokens:\n") + fmt.Fprintf(w, "ID\tISSUED-AT\tEXPIRES-AT\n") + for _, token := range role.JWTTokens { + expiresAt := "" + if token.ExpiresAt > 0 { + expiresAt = humanizeTimestamp(token.ExpiresAt) + } + fmt.Fprintf(w, "%d\t%s\t%s\n", token.IssuedAt, humanizeTimestamp(token.IssuedAt), expiresAt) + } + _ = w.Flush() + }, + } + return command +} + +func humanizeTimestamp(epoch int64) string { + ts := time.Unix(epoch, 0) + return fmt.Sprintf("%s (%s)", ts.Format(time.RFC3339), humanize.Time(ts)) +} + // NewProjectCreateCommand returns a new instance of an `argocd proj create` command func NewProjectCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { var ( diff --git a/pkg/apis/application/v1alpha1/generated.pb.go b/pkg/apis/application/v1alpha1/generated.pb.go index a72ea8c8b7867..c5be0bc4bc0b6 100644 --- a/pkg/apis/application/v1alpha1/generated.pb.go +++ b/pkg/apis/application/v1alpha1/generated.pb.go @@ -28,8 +28,10 @@ DeploymentInfo HealthStatus HookStatus + JWTToken Operation OperationState + ProjectRole Repository RepositoryList ResourceDetails @@ -149,61 +151,69 @@ func (m *HookStatus) Reset() { *m = HookStatus{} } func (*HookStatus) ProtoMessage() {} func (*HookStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{19} } +func (m *JWTToken) Reset() { *m = JWTToken{} } +func (*JWTToken) ProtoMessage() {} +func (*JWTToken) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{20} } + func (m *Operation) Reset() { *m = Operation{} } func (*Operation) ProtoMessage() {} -func (*Operation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{20} } +func (*Operation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} } func (m *OperationState) Reset() { *m = OperationState{} } func (*OperationState) ProtoMessage() {} -func (*OperationState) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} } +func (*OperationState) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } + +func (m *ProjectRole) Reset() { *m = ProjectRole{} } +func (*ProjectRole) ProtoMessage() {} +func (*ProjectRole) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } func (m *Repository) Reset() { *m = Repository{} } func (*Repository) ProtoMessage() {} -func (*Repository) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{22} } +func (*Repository) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } func (m *RepositoryList) Reset() { *m = RepositoryList{} } func (*RepositoryList) ProtoMessage() {} -func (*RepositoryList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{23} } +func (*RepositoryList) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } func (m *ResourceDetails) Reset() { *m = ResourceDetails{} } func (*ResourceDetails) ProtoMessage() {} -func (*ResourceDetails) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{24} } +func (*ResourceDetails) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } func (m *ResourceNode) Reset() { *m = ResourceNode{} } func (*ResourceNode) ProtoMessage() {} -func (*ResourceNode) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{25} } +func (*ResourceNode) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } func (m *ResourceState) Reset() { *m = ResourceState{} } func (*ResourceState) ProtoMessage() {} -func (*ResourceState) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{26} } +func (*ResourceState) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{28} } func (m *RollbackOperation) Reset() { *m = RollbackOperation{} } func (*RollbackOperation) ProtoMessage() {} -func (*RollbackOperation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{27} } +func (*RollbackOperation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } func (m *SyncOperation) Reset() { *m = SyncOperation{} } func (*SyncOperation) ProtoMessage() {} -func (*SyncOperation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{28} } +func (*SyncOperation) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } func (m *SyncOperationResult) Reset() { *m = SyncOperationResult{} } func (*SyncOperationResult) ProtoMessage() {} -func (*SyncOperationResult) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{29} } +func (*SyncOperationResult) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } func (m *SyncStrategy) Reset() { *m = SyncStrategy{} } func (*SyncStrategy) ProtoMessage() {} -func (*SyncStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{30} } +func (*SyncStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{32} } func (m *SyncStrategyApply) Reset() { *m = SyncStrategyApply{} } func (*SyncStrategyApply) ProtoMessage() {} -func (*SyncStrategyApply) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{31} } +func (*SyncStrategyApply) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{33} } func (m *SyncStrategyHook) Reset() { *m = SyncStrategyHook{} } func (*SyncStrategyHook) ProtoMessage() {} -func (*SyncStrategyHook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{32} } +func (*SyncStrategyHook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{34} } func (m *TLSClientConfig) Reset() { *m = TLSClientConfig{} } func (*TLSClientConfig) ProtoMessage() {} -func (*TLSClientConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{33} } +func (*TLSClientConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{35} } func init() { proto.RegisterType((*AppProject)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.AppProject") @@ -226,8 +236,10 @@ func init() { proto.RegisterType((*DeploymentInfo)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.DeploymentInfo") proto.RegisterType((*HealthStatus)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.HealthStatus") proto.RegisterType((*HookStatus)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.HookStatus") + proto.RegisterType((*JWTToken)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.JWTToken") proto.RegisterType((*Operation)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.Operation") proto.RegisterType((*OperationState)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.OperationState") + proto.RegisterType((*ProjectRole)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ProjectRole") proto.RegisterType((*Repository)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.Repository") proto.RegisterType((*RepositoryList)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.RepositoryList") proto.RegisterType((*ResourceDetails)(nil), "github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ResourceDetails") @@ -359,6 +371,18 @@ func (m *AppProjectSpec) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGenerated(dAtA, i, uint64(len(m.Description))) i += copy(dAtA[i:], m.Description) + if len(m.Roles) > 0 { + for _, msg := range m.Roles { + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -1072,6 +1096,30 @@ func (m *HookStatus) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *JWTToken) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *JWTToken) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.IssuedAt)) + dAtA[i] = 0x10 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ExpiresAt)) + return i, nil +} + func (m *Operation) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1182,6 +1230,59 @@ func (m *OperationState) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *ProjectRole) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProjectRole) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Description))) + i += copy(dAtA[i:], m.Description) + if len(m.Policies) > 0 { + for _, s := range m.Policies { + dAtA[i] = 0x1a + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if len(m.JWTTokens) > 0 { + for _, msg := range m.JWTTokens { + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *Repository) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1703,6 +1804,12 @@ func (m *AppProjectSpec) Size() (n int) { } l = len(m.Description) n += 1 + l + sovGenerated(uint64(l)) + if len(m.Roles) > 0 { + for _, e := range m.Roles { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -1973,6 +2080,14 @@ func (m *HookStatus) Size() (n int) { return n } +func (m *JWTToken) Size() (n int) { + var l int + _ = l + n += 1 + sovGenerated(uint64(m.IssuedAt)) + n += 1 + sovGenerated(uint64(m.ExpiresAt)) + return n +} + func (m *Operation) Size() (n int) { var l int _ = l @@ -2013,6 +2128,28 @@ func (m *OperationState) Size() (n int) { return n } +func (m *ProjectRole) Size() (n int) { + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Description) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Policies) > 0 { + for _, s := range m.Policies { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if len(m.JWTTokens) > 0 { + for _, e := range m.JWTTokens { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + func (m *Repository) Size() (n int) { var l int _ = l @@ -2229,6 +2366,7 @@ func (this *AppProjectSpec) String() string { `SourceRepos:` + fmt.Sprintf("%v", this.SourceRepos) + `,`, `Destinations:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Destinations), "ApplicationDestination", "ApplicationDestination", 1), `&`, ``, 1) + `,`, `Description:` + fmt.Sprintf("%v", this.Description) + `,`, + `Roles:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Roles), "ProjectRole", "ProjectRole", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -2446,6 +2584,17 @@ func (this *HookStatus) String() string { }, "") return s } +func (this *JWTToken) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&JWTToken{`, + `IssuedAt:` + fmt.Sprintf("%v", this.IssuedAt) + `,`, + `ExpiresAt:` + fmt.Sprintf("%v", this.ExpiresAt) + `,`, + `}`, + }, "") + return s +} func (this *Operation) String() string { if this == nil { return "nil" @@ -2473,6 +2622,19 @@ func (this *OperationState) String() string { }, "") return s } +func (this *ProjectRole) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ProjectRole{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Description:` + fmt.Sprintf("%v", this.Description) + `,`, + `Policies:` + fmt.Sprintf("%v", this.Policies) + `,`, + `JWTTokens:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.JWTTokens), "JWTToken", "JWTToken", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} func (this *Repository) String() string { if this == nil { return "nil" @@ -2966,6 +3128,37 @@ func (m *AppProjectSpec) Unmarshal(dAtA []byte) error { } m.Description = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Roles", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Roles = append(m.Roles, ProjectRole{}) + if err := m.Roles[len(m.Roles)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -5613,6 +5806,94 @@ func (m *HookStatus) Unmarshal(dAtA []byte) error { } return nil } +func (m *JWTToken) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JWTToken: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JWTToken: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IssuedAt", wireType) + } + m.IssuedAt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.IssuedAt |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpiresAt", wireType) + } + m.ExpiresAt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExpiresAt |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Operation) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -5996,6 +6277,174 @@ func (m *OperationState) Unmarshal(dAtA []byte) error { } return nil } +func (m *ProjectRole) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProjectRole: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProjectRole: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Policies", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Policies = append(m.Policies, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JWTTokens", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.JWTTokens = append(m.JWTTokens, JWTToken{}) + if err := m.JWTTokens[len(m.JWTTokens)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Repository) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -7776,158 +8225,167 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 2446 bytes of a gzipped FileDescriptorProto + // 2577 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x59, 0x4b, 0x8c, 0x1c, 0x47, - 0x19, 0x76, 0xcf, 0x6b, 0x67, 0xfe, 0xd9, 0x87, 0x5d, 0x79, 0x30, 0x38, 0xd2, 0xee, 0xaa, 0xc3, - 0xc3, 0xa0, 0x64, 0x06, 0x1b, 0x02, 0xe6, 0x21, 0x24, 0xcf, 0xae, 0x1d, 0x6f, 0xd6, 0x8f, 0xa5, - 0x66, 0x13, 0xa4, 0x10, 0x05, 0xda, 0x3d, 0xb5, 0x33, 0xed, 0x99, 0xe9, 0xee, 0x74, 0xd5, 0x8c, - 0x35, 0x12, 0x41, 0x41, 0x08, 0x29, 0xbc, 0x24, 0x10, 0x42, 0x5c, 0x39, 0x70, 0x42, 0x48, 0x48, - 0x88, 0x13, 0x12, 0x07, 0x38, 0x20, 0x1f, 0x73, 0x00, 0x11, 0x05, 0xb4, 0xc2, 0x9b, 0x4b, 0x24, - 0x0e, 0xdc, 0x73, 0x42, 0xf5, 0xe8, 0xae, 0xea, 0x9e, 0x5d, 0x76, 0xed, 0x69, 0x1b, 0x72, 0xeb, - 0xfe, 0xff, 0xbf, 0xff, 0xef, 0xaf, 0xbf, 0xfe, 0xfa, 0x1f, 0xd5, 0xb0, 0xd5, 0xf3, 0x58, 0x7f, - 0x7c, 0xab, 0xe9, 0x06, 0xa3, 0x96, 0x13, 0xf5, 0x82, 0x30, 0x0a, 0x6e, 0x8b, 0x87, 0x67, 0xdd, - 0x6e, 0x2b, 0x1c, 0xf4, 0x5a, 0x4e, 0xe8, 0xd1, 0x96, 0x13, 0x86, 0x43, 0xcf, 0x75, 0x98, 0x17, - 0xf8, 0xad, 0xc9, 0x79, 0x67, 0x18, 0xf6, 0x9d, 0xf3, 0xad, 0x1e, 0xf1, 0x49, 0xe4, 0x30, 0xd2, - 0x6d, 0x86, 0x51, 0xc0, 0x02, 0xf4, 0x79, 0xad, 0xaa, 0x19, 0xab, 0x12, 0x0f, 0x5f, 0x77, 0xbb, - 0xcd, 0x70, 0xd0, 0x6b, 0x72, 0x55, 0x4d, 0x43, 0x55, 0x33, 0x56, 0x75, 0xf6, 0x59, 0xc3, 0x8a, - 0x5e, 0xd0, 0x0b, 0x5a, 0x42, 0xe3, 0xad, 0xf1, 0x9e, 0x78, 0x13, 0x2f, 0xe2, 0x49, 0x22, 0x9d, - 0xfd, 0xcc, 0xe0, 0x22, 0x6d, 0x7a, 0x01, 0xb7, 0x6d, 0xe4, 0xb8, 0x7d, 0xcf, 0x27, 0xd1, 0x54, - 0x1b, 0x3b, 0x22, 0xcc, 0x69, 0x4d, 0x66, 0xec, 0x3b, 0xdb, 0x3a, 0xea, 0xab, 0x68, 0xec, 0x33, - 0x6f, 0x44, 0x66, 0x3e, 0xf8, 0xec, 0x71, 0x1f, 0x50, 0xb7, 0x4f, 0x46, 0xce, 0xcc, 0x77, 0x9f, - 0x3e, 0xea, 0xbb, 0x31, 0xf3, 0x86, 0x2d, 0xcf, 0x67, 0x94, 0x45, 0xd9, 0x8f, 0xec, 0xbf, 0x5b, - 0x00, 0x97, 0xc2, 0x70, 0x27, 0x0a, 0x6e, 0x13, 0x97, 0xa1, 0x6f, 0x40, 0x95, 0xaf, 0xa3, 0xeb, - 0x30, 0xa7, 0x61, 0xad, 0x5b, 0xe7, 0xea, 0x17, 0x3e, 0xd5, 0x94, 0x6a, 0x9b, 0xa6, 0x5a, 0xed, - 0x57, 0x2e, 0xdd, 0x9c, 0x9c, 0x6f, 0xde, 0xbc, 0xc5, 0xbf, 0xbf, 0x4e, 0x98, 0xd3, 0x46, 0x77, - 0xf7, 0xd7, 0x4e, 0x1d, 0xec, 0xaf, 0x81, 0xa6, 0xe1, 0x44, 0x2b, 0x1a, 0x40, 0x89, 0x86, 0xc4, - 0x6d, 0x14, 0x84, 0xf6, 0xad, 0xe6, 0x03, 0xef, 0x5e, 0x53, 0x9b, 0xdd, 0x09, 0x89, 0xdb, 0x5e, - 0x54, 0xb0, 0x25, 0xfe, 0x86, 0x05, 0x88, 0xfd, 0x8e, 0x05, 0xcb, 0x5a, 0xec, 0x9a, 0x47, 0x19, - 0x7a, 0x65, 0x66, 0x85, 0xcd, 0x93, 0xad, 0x90, 0x7f, 0x2d, 0xd6, 0x77, 0x5a, 0x01, 0x55, 0x63, - 0x8a, 0xb1, 0xba, 0xdb, 0x50, 0xf6, 0x18, 0x19, 0xd1, 0x46, 0x61, 0xbd, 0x78, 0xae, 0x7e, 0xe1, - 0x72, 0x2e, 0xcb, 0x6b, 0x2f, 0x29, 0xc4, 0xf2, 0x16, 0xd7, 0x8d, 0x25, 0x84, 0xfd, 0x66, 0xc1, - 0x5c, 0x1c, 0x5f, 0x35, 0x3a, 0x0f, 0x75, 0x1a, 0x8c, 0x23, 0x97, 0x60, 0x12, 0x06, 0xb4, 0x61, - 0xad, 0x17, 0xcf, 0xd5, 0xda, 0x2b, 0x07, 0xfb, 0x6b, 0xf5, 0x8e, 0x26, 0x63, 0x53, 0x06, 0xfd, - 0xc0, 0x82, 0xc5, 0x2e, 0xa1, 0xcc, 0xf3, 0x05, 0x7e, 0x6c, 0xf9, 0x57, 0xe6, 0xb3, 0x3c, 0x26, - 0x6e, 0x6a, 0xcd, 0xed, 0xc7, 0xd5, 0x2a, 0x16, 0x0d, 0x22, 0xc5, 0x29, 0x70, 0xf4, 0x1c, 0xd4, - 0xbb, 0x84, 0xba, 0x91, 0x17, 0xf2, 0xf7, 0x46, 0x71, 0xdd, 0x3a, 0x57, 0x6b, 0x3f, 0xa6, 0x3e, - 0xac, 0x6f, 0x6a, 0x16, 0x36, 0xe5, 0xec, 0x3f, 0x17, 0xa1, 0x6e, 0xa0, 0x3e, 0x82, 0x30, 0x1e, - 0xa6, 0xc2, 0xf8, 0x85, 0x7c, 0xbc, 0x75, 0x54, 0x1c, 0x23, 0x06, 0x15, 0xca, 0x1c, 0x36, 0xa6, - 0xc2, 0x23, 0xf5, 0x0b, 0xd7, 0x72, 0xc2, 0x13, 0x3a, 0xdb, 0xcb, 0x0a, 0xb1, 0x22, 0xdf, 0xb1, - 0xc2, 0x42, 0xaf, 0x41, 0x2d, 0x08, 0x79, 0xb6, 0xe0, 0x5b, 0x51, 0x12, 0xc0, 0x9b, 0x73, 0x00, - 0xdf, 0x8c, 0x75, 0xb5, 0x97, 0x0e, 0xf6, 0xd7, 0x6a, 0xc9, 0x2b, 0xd6, 0x28, 0xb6, 0x0b, 0x8f, - 0x1b, 0xf6, 0x6d, 0x04, 0x7e, 0xd7, 0x13, 0x1b, 0xba, 0x0e, 0x25, 0x36, 0x0d, 0x89, 0xd8, 0xcc, - 0x9a, 0x76, 0xd1, 0xee, 0x34, 0x24, 0x58, 0x70, 0xd0, 0x27, 0x60, 0x61, 0x44, 0x28, 0x75, 0x7a, - 0x44, 0xec, 0x49, 0xad, 0xbd, 0xa2, 0x84, 0x16, 0xae, 0x4b, 0x32, 0x8e, 0xf9, 0xf6, 0x6b, 0xf0, - 0xe4, 0xe1, 0x21, 0x8a, 0x3e, 0x06, 0x15, 0x4a, 0xa2, 0x09, 0x89, 0x14, 0x90, 0xf6, 0x8c, 0xa0, - 0x62, 0xc5, 0x45, 0x2d, 0xa8, 0xf9, 0xce, 0x88, 0xd0, 0xd0, 0x71, 0x63, 0xb8, 0x33, 0x4a, 0xb4, - 0x76, 0x23, 0x66, 0x60, 0x2d, 0x63, 0xff, 0xc3, 0x82, 0x15, 0x03, 0xf3, 0x11, 0x64, 0xa2, 0x41, - 0x3a, 0x13, 0x5d, 0xc9, 0x27, 0x62, 0x8e, 0x48, 0x45, 0x7f, 0x2c, 0xc2, 0x19, 0x33, 0xae, 0x44, - 0x7e, 0xe1, 0x5b, 0x12, 0x91, 0x30, 0x78, 0x11, 0x5f, 0x53, 0xee, 0x4c, 0xb6, 0x04, 0x4b, 0x32, - 0x8e, 0xf9, 0x7c, 0x7f, 0x43, 0x87, 0xf5, 0x95, 0x2f, 0x93, 0xfd, 0xdd, 0x71, 0x58, 0x1f, 0x0b, - 0x0e, 0xcf, 0x0c, 0xc4, 0x9f, 0x78, 0x51, 0xe0, 0x8f, 0x88, 0xcf, 0xb2, 0x99, 0xe1, 0xb2, 0x66, - 0x61, 0x53, 0x0e, 0x7d, 0x19, 0x96, 0x99, 0x13, 0xf5, 0x08, 0xc3, 0x64, 0xe2, 0xd1, 0x38, 0x90, - 0x6b, 0xed, 0x27, 0xd5, 0x97, 0xcb, 0xbb, 0x29, 0x2e, 0xce, 0x48, 0xa3, 0xdf, 0x59, 0xf0, 0x94, - 0x1b, 0x8c, 0xc2, 0xc0, 0x27, 0x3e, 0xdb, 0x71, 0x22, 0x67, 0x44, 0x18, 0x89, 0x6e, 0x4e, 0x48, - 0x14, 0x79, 0x5d, 0x42, 0x1b, 0x65, 0xe1, 0xdd, 0xeb, 0x73, 0x78, 0x77, 0x63, 0x46, 0x7b, 0xfb, - 0x69, 0x65, 0xdc, 0x53, 0x1b, 0x47, 0x23, 0xe3, 0xff, 0x66, 0x16, 0x2f, 0x04, 0x13, 0x67, 0x38, - 0x26, 0xf4, 0x8a, 0x37, 0x24, 0xb4, 0x51, 0xd1, 0x85, 0xe0, 0x25, 0x4d, 0xc6, 0xa6, 0x8c, 0xfd, - 0x87, 0x42, 0x2a, 0x44, 0x3b, 0x71, 0xde, 0x11, 0x7b, 0xa9, 0x02, 0x34, 0xaf, 0xbc, 0x23, 0x74, - 0x1a, 0xa7, 0x4b, 0xd6, 0x23, 0x85, 0x85, 0xde, 0xb4, 0x44, 0x15, 0x88, 0x4f, 0xa5, 0xca, 0xb1, - 0x0f, 0xa1, 0x22, 0x99, 0x85, 0x25, 0x26, 0x62, 0x13, 0x9a, 0x87, 0x70, 0x28, 0xeb, 0xab, 0x8a, - 0xb8, 0x24, 0x84, 0x55, 0xd9, 0xc5, 0x31, 0xdf, 0xfe, 0x45, 0x25, 0x7d, 0x06, 0x64, 0x0e, 0xfd, - 0x89, 0x05, 0xa7, 0xf9, 0x46, 0x39, 0x91, 0x47, 0x03, 0x1f, 0x13, 0x3a, 0x1e, 0x32, 0xe5, 0xcc, - 0xed, 0x39, 0x83, 0xc6, 0x54, 0xd9, 0x6e, 0x28, 0xbb, 0x4e, 0x67, 0x39, 0x78, 0x06, 0x1e, 0x31, - 0x58, 0xe8, 0x7b, 0x94, 0x05, 0xd1, 0x54, 0x25, 0x87, 0x79, 0xba, 0xb0, 0x4d, 0x12, 0x0e, 0x83, - 0x29, 0x3f, 0x6b, 0x5b, 0xfe, 0x5e, 0xa0, 0xfd, 0x73, 0x55, 0x22, 0xe0, 0x18, 0x0a, 0x7d, 0xdb, - 0x02, 0x08, 0xe3, 0x48, 0xe5, 0x85, 0xec, 0x21, 0x1c, 0x9c, 0xa4, 0x66, 0x27, 0x24, 0x8a, 0x0d, - 0x50, 0x14, 0x40, 0xa5, 0x4f, 0x9c, 0x21, 0xeb, 0xab, 0x72, 0xf6, 0xfc, 0x1c, 0xf0, 0x57, 0x85, - 0xa2, 0x6c, 0x09, 0x95, 0x54, 0xac, 0x60, 0xd0, 0x77, 0x2d, 0x58, 0x4e, 0xaa, 0x1b, 0x97, 0x25, - 0x8d, 0xf2, 0xdc, 0x8d, 0xef, 0xcd, 0x94, 0xc2, 0x36, 0xe2, 0x69, 0x2c, 0x4d, 0xc3, 0x19, 0x50, - 0xf4, 0x1d, 0x0b, 0xc0, 0x8d, 0xab, 0xa9, 0xcc, 0x07, 0xf5, 0x0b, 0x37, 0xf3, 0x39, 0x51, 0x49, - 0x95, 0xd6, 0xee, 0x4f, 0x48, 0x14, 0x1b, 0xb0, 0xf6, 0xbb, 0x16, 0x3c, 0x61, 0x7c, 0xf8, 0x55, - 0x87, 0xb9, 0xfd, 0xcb, 0x13, 0x9e, 0xa6, 0xb7, 0x53, 0xf5, 0xfd, 0x73, 0x66, 0x7d, 0x7f, 0x7f, - 0x7f, 0xed, 0xe3, 0x47, 0x4d, 0x36, 0x77, 0xb8, 0x86, 0xa6, 0x50, 0x61, 0xb4, 0x02, 0xaf, 0x43, - 0xdd, 0xb0, 0x59, 0xa5, 0x8f, 0xbc, 0x0a, 0x60, 0x92, 0x33, 0x0c, 0x22, 0x36, 0xf1, 0xec, 0xbf, - 0x16, 0x60, 0x61, 0x63, 0x38, 0xa6, 0x8c, 0x44, 0x27, 0x6e, 0x28, 0xd6, 0xa1, 0xc4, 0x9b, 0x85, - 0x6c, 0xfd, 0xe3, 0xbd, 0x04, 0x16, 0x1c, 0x14, 0x42, 0xc5, 0x0d, 0xfc, 0x3d, 0xaf, 0xa7, 0x5a, - 0xc0, 0xab, 0xf3, 0x9c, 0x1c, 0x69, 0xdd, 0x86, 0xd0, 0xa7, 0x6d, 0x92, 0xef, 0x58, 0xe1, 0xa0, - 0x1f, 0x59, 0xb0, 0xe2, 0x06, 0xbe, 0x4f, 0x5c, 0x1d, 0xbc, 0xa5, 0xb9, 0xdb, 0xdd, 0x8d, 0xb4, - 0xc6, 0xf6, 0x87, 0x14, 0xfa, 0x4a, 0x86, 0x81, 0xb3, 0xd8, 0xf6, 0x6f, 0x0b, 0xb0, 0x94, 0xb2, - 0x1c, 0x3d, 0x03, 0xd5, 0x31, 0x25, 0x91, 0xf0, 0x9c, 0xf4, 0x6f, 0xd2, 0x11, 0xbd, 0xa8, 0xe8, - 0x38, 0x91, 0xe0, 0xd2, 0xa1, 0x43, 0xe9, 0x9d, 0x20, 0xea, 0x2a, 0x3f, 0x27, 0xd2, 0x3b, 0x8a, - 0x8e, 0x13, 0x09, 0xde, 0x6f, 0xdc, 0x22, 0x4e, 0x44, 0xa2, 0xdd, 0x60, 0x40, 0x66, 0x26, 0x91, - 0xb6, 0x66, 0x61, 0x53, 0x4e, 0x38, 0x8d, 0x0d, 0xe9, 0xc6, 0xd0, 0x23, 0x3e, 0x93, 0x66, 0xe6, - 0xe0, 0xb4, 0xdd, 0x6b, 0x1d, 0x53, 0xa3, 0x76, 0x5a, 0x86, 0x81, 0xb3, 0xd8, 0xf6, 0x5f, 0x2c, - 0xa8, 0x2b, 0xa7, 0x3d, 0x82, 0xa6, 0xb3, 0x97, 0x6e, 0x3a, 0xdb, 0xf3, 0xc7, 0xe8, 0x11, 0x0d, - 0xe7, 0xaf, 0x8b, 0x30, 0x53, 0xe9, 0xd0, 0xab, 0x3c, 0xc7, 0x71, 0x1a, 0xe9, 0x5e, 0x8a, 0x8b, - 0xec, 0x27, 0x4f, 0xb6, 0xba, 0x5d, 0x6f, 0x44, 0xcc, 0xf4, 0x15, 0x6b, 0xc1, 0x86, 0x46, 0xf4, - 0x86, 0xa5, 0x01, 0x76, 0x03, 0x95, 0x57, 0xf2, 0x6d, 0x89, 0x66, 0x4c, 0xd8, 0x0d, 0xb0, 0x81, - 0x89, 0xbe, 0x90, 0x0c, 0x82, 0x65, 0x11, 0x90, 0x76, 0x7a, 0x74, 0x7b, 0x3f, 0xd5, 0x00, 0x64, - 0xc6, 0xb9, 0x29, 0xd4, 0x22, 0x22, 0x5b, 0xac, 0xb8, 0x02, 0xcc, 0x93, 0x44, 0xb0, 0xd2, 0x25, - 0x8f, 0x71, 0x32, 0xfe, 0xc4, 0x64, 0x8a, 0x35, 0x9a, 0xfd, 0x43, 0x0b, 0xd0, 0x6c, 0xb9, 0xe6, - 0x63, 0x54, 0xd2, 0xc4, 0xaa, 0x03, 0x9c, 0xe8, 0x49, 0xc4, 0xb1, 0x96, 0x39, 0x41, 0x9a, 0x7c, - 0x1a, 0xca, 0xa2, 0xa9, 0x55, 0x07, 0x36, 0x89, 0x1e, 0xd1, 0xf6, 0x62, 0xc9, 0xb3, 0xff, 0x64, - 0x41, 0x36, 0xdd, 0x88, 0x4c, 0x2d, 0x3d, 0x9b, 0xcd, 0xd4, 0x69, 0x2f, 0x9e, 0x7c, 0xce, 0x44, - 0xaf, 0x40, 0xdd, 0x61, 0x8c, 0x8c, 0x42, 0x26, 0x02, 0xb2, 0x78, 0xdf, 0x01, 0xb9, 0xcc, 0x23, - 0xe1, 0x7a, 0xd0, 0xf5, 0xf6, 0x3c, 0x11, 0x8c, 0xa6, 0x3a, 0xfb, 0xbd, 0x22, 0x2c, 0xa7, 0x9b, - 0x2f, 0x34, 0x86, 0x8a, 0x68, 0x76, 0xe4, 0xcd, 0x4f, 0xee, 0xdd, 0x55, 0xe2, 0x12, 0x41, 0xa2, - 0x58, 0x81, 0xf1, 0xc4, 0x1a, 0xc5, 0xd3, 0x55, 0x26, 0xb1, 0x26, 0x73, 0x55, 0x22, 0x71, 0xec, - 0x44, 0x55, 0xfc, 0xff, 0x9c, 0xa8, 0x5e, 0x05, 0xe8, 0x0a, 0x6f, 0x8b, 0xbd, 0x2c, 0x3d, 0x78, - 0x72, 0xd9, 0x4c, 0xb4, 0x60, 0x43, 0x23, 0x3a, 0x0b, 0x05, 0xaf, 0x2b, 0x4e, 0x75, 0xb1, 0x0d, - 0x4a, 0xb6, 0xb0, 0xb5, 0x89, 0x0b, 0x5e, 0xd7, 0xa6, 0xb0, 0x68, 0x76, 0x9b, 0x27, 0x8e, 0xd5, - 0x2f, 0xc2, 0x92, 0x7c, 0xda, 0x24, 0xcc, 0xf1, 0x86, 0x54, 0xed, 0xce, 0x13, 0x4a, 0x7c, 0xa9, - 0x63, 0x32, 0x71, 0x5a, 0xd6, 0xfe, 0x79, 0x01, 0xe0, 0x6a, 0x10, 0x0c, 0x14, 0x66, 0x7c, 0xf4, - 0xac, 0x23, 0x8f, 0xde, 0x3a, 0x94, 0x06, 0x9e, 0xdf, 0xcd, 0x1e, 0xce, 0x6d, 0xcf, 0xef, 0x62, - 0xc1, 0x41, 0x17, 0x00, 0x9c, 0xd0, 0x7b, 0x89, 0x44, 0x54, 0x5f, 0xee, 0x25, 0x7e, 0xb9, 0xb4, - 0xb3, 0xa5, 0x38, 0xd8, 0x90, 0x42, 0xcf, 0xa8, 0xce, 0x50, 0x8e, 0xed, 0x8d, 0x4c, 0x67, 0x58, - 0xe5, 0x16, 0x1a, 0xad, 0xdf, 0xc5, 0x4c, 0x7e, 0x5c, 0x9f, 0xc9, 0x8f, 0xba, 0x53, 0xde, 0xe9, - 0x3b, 0x94, 0x1c, 0x76, 0xae, 0x2b, 0xc7, 0xdc, 0x1f, 0xfd, 0xcb, 0x02, 0x7d, 0x7b, 0x85, 0xf6, - 0xa0, 0x44, 0xa7, 0xbe, 0xab, 0xea, 0xcd, 0x3c, 0x19, 0xb5, 0x33, 0xf5, 0x5d, 0x7d, 0x49, 0x56, - 0x15, 0x77, 0x80, 0x53, 0xdf, 0xc5, 0x42, 0x3f, 0x9a, 0x40, 0x35, 0x0a, 0x86, 0xc3, 0x5b, 0x8e, - 0x3b, 0xc8, 0xa1, 0xf4, 0x60, 0xa5, 0x4a, 0xe3, 0x2d, 0x8a, 0xf3, 0xaa, 0xc8, 0x38, 0xc1, 0xb2, - 0x7f, 0x53, 0x86, 0xcc, 0x74, 0x81, 0xc6, 0xe6, 0xc5, 0xa0, 0x95, 0xe3, 0xc5, 0x60, 0x92, 0xfd, - 0x0f, 0xbb, 0x1c, 0x44, 0xcf, 0x41, 0x39, 0xe4, 0x7b, 0xa6, 0x22, 0x6c, 0x2d, 0xce, 0xed, 0x62, - 0x23, 0x0f, 0xd9, 0x5a, 0x29, 0x6d, 0xee, 0x6c, 0xf1, 0x98, 0x8c, 0xfd, 0x2d, 0x00, 0xee, 0x6b, - 0x35, 0xa6, 0xcb, 0x43, 0x7e, 0x23, 0xaf, 0x1d, 0x55, 0x93, 0xba, 0x48, 0xea, 0x9d, 0x04, 0x05, - 0x1b, 0x88, 0xe8, 0xfb, 0x16, 0x2c, 0xc7, 0x8e, 0x57, 0x46, 0x94, 0x1f, 0x8a, 0x11, 0x62, 0x66, - 0xc4, 0x29, 0x24, 0x9c, 0x41, 0x46, 0x5f, 0x83, 0x1a, 0x65, 0x4e, 0x24, 0x8b, 0x57, 0xe5, 0xbe, - 0x13, 0x5e, 0xb2, 0x97, 0x9d, 0x58, 0x09, 0xd6, 0xfa, 0xd0, 0xcb, 0x00, 0x7b, 0x9e, 0xef, 0xd1, - 0xbe, 0xd0, 0xbe, 0xf0, 0x60, 0xa5, 0xf1, 0x4a, 0xa2, 0x01, 0x1b, 0xda, 0xec, 0xbf, 0x15, 0x00, - 0xc4, 0xcf, 0x0d, 0x4f, 0x5c, 0x3c, 0xac, 0x43, 0x29, 0x22, 0x61, 0x90, 0xcd, 0x5c, 0x5c, 0x02, - 0x0b, 0x4e, 0x6a, 0x8e, 0x28, 0xdc, 0xd7, 0x1c, 0x51, 0x3c, 0x76, 0x8e, 0xe0, 0x39, 0x98, 0xf6, - 0x77, 0x22, 0x6f, 0xe2, 0x30, 0xb2, 0x4d, 0xa6, 0x2a, 0x91, 0xe9, 0x1c, 0xdc, 0xb9, 0xaa, 0x99, - 0x38, 0x2d, 0x7b, 0xe8, 0x08, 0x56, 0xfe, 0x1f, 0x8e, 0x60, 0xef, 0x58, 0xb0, 0xac, 0x3d, 0xfb, - 0xc1, 0xfa, 0x9f, 0xa6, 0xed, 0x3e, 0x62, 0xa6, 0xf8, 0xb7, 0x05, 0x2b, 0x71, 0xf7, 0xaa, 0x8a, - 0x60, 0x2e, 0x55, 0x2f, 0xf5, 0xb3, 0xa0, 0x78, 0xfc, 0xcf, 0x02, 0x33, 0x61, 0x95, 0x8e, 0x49, - 0x58, 0x5f, 0xca, 0xd4, 0xbb, 0x8f, 0xcc, 0xd4, 0x3b, 0x94, 0xf4, 0xe9, 0x53, 0xdf, 0x4d, 0xf7, - 0x07, 0xf6, 0xaf, 0x2c, 0x58, 0x8c, 0xd9, 0x37, 0x82, 0xae, 0xe8, 0x9e, 0xa9, 0x08, 0x32, 0x2b, - 0xdd, 0x3d, 0xcb, 0x70, 0x90, 0x3c, 0x34, 0x86, 0xaa, 0xdb, 0xf7, 0x86, 0xdd, 0x88, 0xf8, 0x6a, - 0x5b, 0x9e, 0xcf, 0x61, 0x8c, 0xe0, 0xf8, 0x3a, 0x14, 0x36, 0x14, 0x00, 0x4e, 0xa0, 0xec, 0xdf, - 0x17, 0x61, 0x29, 0x35, 0x73, 0xf0, 0x11, 0x5d, 0xde, 0xd6, 0x77, 0x0c, 0x9b, 0x93, 0x11, 0x7d, - 0x57, 0xb3, 0xb0, 0x29, 0xc7, 0xf7, 0x63, 0xe8, 0x4d, 0xa4, 0x8e, 0xec, 0xcf, 0x9b, 0x6b, 0x31, - 0x03, 0x6b, 0x19, 0x63, 0xe8, 0x2a, 0xde, 0xf7, 0xd0, 0xf5, 0x53, 0x0b, 0x90, 0x58, 0x02, 0xd7, - 0x9c, 0xcc, 0x46, 0x8d, 0x52, 0xbe, 0x7e, 0x3b, 0xab, 0x2c, 0x42, 0x1b, 0x33, 0x50, 0xf8, 0x10, - 0x78, 0xe3, 0x1e, 0xb4, 0xfc, 0x48, 0xee, 0x41, 0xed, 0x6f, 0xc2, 0x99, 0x99, 0x8e, 0x43, 0xb5, - 0xbc, 0xd6, 0x61, 0x2d, 0x2f, 0x8f, 0xc4, 0x30, 0x1a, 0xfb, 0x72, 0x83, 0xaa, 0x3a, 0x12, 0x77, - 0x38, 0x11, 0x4b, 0x1e, 0xef, 0x83, 0xbb, 0xd1, 0x14, 0x8f, 0x65, 0x2f, 0x59, 0xd5, 0xe8, 0x9b, - 0x82, 0x8a, 0x15, 0xd7, 0xfe, 0x5e, 0x01, 0x96, 0x52, 0x55, 0x30, 0x35, 0xb2, 0x58, 0xc7, 0x8e, - 0x2c, 0x79, 0x1a, 0x83, 0x5e, 0x87, 0x45, 0x2a, 0x8e, 0x62, 0xe4, 0x30, 0xd2, 0x9b, 0xe6, 0x70, - 0x13, 0xdd, 0x31, 0xd4, 0xb5, 0x4f, 0x1f, 0xec, 0xaf, 0x2d, 0x9a, 0x14, 0x9c, 0x82, 0xb3, 0x7f, - 0x59, 0x80, 0xc7, 0x0e, 0xe9, 0x08, 0xd0, 0x1d, 0xf3, 0x76, 0x40, 0x8e, 0x8f, 0x2f, 0xe4, 0x10, - 0x9e, 0x2a, 0x91, 0xca, 0x5f, 0xbe, 0x87, 0xdd, 0x0d, 0xdc, 0xe7, 0xf4, 0xb8, 0x07, 0xe5, 0x7e, - 0x10, 0x0c, 0xe2, 0x31, 0x71, 0x9e, 0x82, 0xa0, 0x87, 0x9b, 0x76, 0x8d, 0xef, 0x26, 0x7f, 0xa7, - 0x58, 0xaa, 0xb7, 0xdf, 0xb3, 0x20, 0xe5, 0x45, 0x34, 0x82, 0x32, 0xd7, 0x32, 0xcd, 0xe1, 0x4f, - 0x98, 0xa9, 0xf7, 0x12, 0xd7, 0x29, 0xf1, 0xc5, 0x23, 0x96, 0x28, 0xc8, 0x83, 0x12, 0x37, 0x44, - 0x75, 0xfa, 0xdb, 0x39, 0xa1, 0xf1, 0x25, 0xca, 0xc1, 0x82, 0x3f, 0x61, 0x01, 0x61, 0x5f, 0x84, - 0x33, 0x33, 0x16, 0xf1, 0x90, 0xdf, 0x0b, 0xe2, 0x1f, 0x7f, 0x46, 0xc8, 0x5f, 0xe1, 0x44, 0x2c, - 0x79, 0xbc, 0x7e, 0x9c, 0xce, 0xaa, 0x47, 0x3f, 0xb3, 0xe0, 0x0c, 0xcd, 0xea, 0x7b, 0x28, 0x5e, - 0xfb, 0xb0, 0x32, 0x6a, 0xd6, 0x7c, 0x3c, 0x6b, 0x01, 0xdf, 0xd1, 0xec, 0x75, 0x29, 0x8f, 0x3d, - 0xcf, 0xa7, 0xc4, 0x1d, 0x47, 0xf1, 0x42, 0x93, 0xd8, 0xdb, 0x52, 0x74, 0x9c, 0x48, 0xf0, 0xf1, - 0x55, 0x5e, 0xd7, 0xdf, 0xd0, 0x8d, 0x62, 0x32, 0xbe, 0x76, 0x12, 0x0e, 0x36, 0xa4, 0xd0, 0x39, - 0xa8, 0xba, 0x24, 0x62, 0x9b, 0xbc, 0x3d, 0xe2, 0x79, 0x61, 0x51, 0xce, 0x59, 0x1b, 0x8a, 0x86, - 0x13, 0x2e, 0xfa, 0x28, 0x2c, 0x0c, 0xc8, 0x54, 0x08, 0x96, 0x84, 0x60, 0x9d, 0x57, 0xfc, 0x6d, - 0x49, 0xc2, 0x31, 0x0f, 0xd9, 0x50, 0x71, 0x1d, 0x21, 0x55, 0x16, 0x52, 0x20, 0x6e, 0xee, 0x2f, - 0x09, 0x21, 0xc5, 0x69, 0x37, 0xef, 0xde, 0x5b, 0x3d, 0xf5, 0xd6, 0xbd, 0xd5, 0x53, 0x6f, 0xdf, - 0x5b, 0x3d, 0xf5, 0xc6, 0xc1, 0xaa, 0x75, 0xf7, 0x60, 0xd5, 0x7a, 0xeb, 0x60, 0xd5, 0x7a, 0xfb, - 0x60, 0xd5, 0xfa, 0xe7, 0xc1, 0xaa, 0xf5, 0xe3, 0x77, 0x57, 0x4f, 0xbd, 0x5c, 0x8d, 0x5d, 0xfb, - 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf5, 0xd2, 0xa5, 0x7e, 0x8e, 0x27, 0x00, 0x00, + 0xf9, 0x77, 0xcf, 0x6b, 0x67, 0xbe, 0xd9, 0x87, 0x5d, 0x79, 0xfc, 0xf7, 0xef, 0x48, 0xbb, 0xab, + 0x36, 0x0f, 0x83, 0x92, 0x19, 0xbc, 0x10, 0x30, 0x0f, 0x21, 0x79, 0x66, 0xed, 0x78, 0xbd, 0x7e, + 0x2c, 0x35, 0x9b, 0x44, 0x0a, 0x51, 0xa0, 0xdd, 0x53, 0x3b, 0xd3, 0x9e, 0x99, 0xee, 0x4e, 0x57, + 0xcd, 0xd8, 0x23, 0x11, 0x14, 0x84, 0x40, 0x3c, 0x25, 0x10, 0x42, 0x5c, 0x39, 0x70, 0x42, 0x48, + 0x48, 0x88, 0x13, 0x12, 0x07, 0x38, 0x20, 0x1f, 0x73, 0x00, 0x11, 0x05, 0xb4, 0xc2, 0x9b, 0x4b, + 0x24, 0x0e, 0x9c, 0xb8, 0xe4, 0x84, 0xea, 0xd1, 0x5d, 0xd5, 0x3d, 0xbb, 0xec, 0xae, 0x67, 0x6c, + 0xe0, 0xd6, 0xfd, 0x7d, 0x5f, 0x7f, 0xbf, 0xaf, 0xbf, 0xfa, 0xea, 0x7b, 0x54, 0xc1, 0x66, 0xc7, + 0x63, 0xdd, 0xe1, 0xed, 0x9a, 0x1b, 0x0c, 0xea, 0x4e, 0xd4, 0x09, 0xc2, 0x28, 0xb8, 0x23, 0x1e, + 0x9e, 0x73, 0xdb, 0xf5, 0xb0, 0xd7, 0xa9, 0x3b, 0xa1, 0x47, 0xeb, 0x4e, 0x18, 0xf6, 0x3d, 0xd7, + 0x61, 0x5e, 0xe0, 0xd7, 0x47, 0x17, 0x9c, 0x7e, 0xd8, 0x75, 0x2e, 0xd4, 0x3b, 0xc4, 0x27, 0x91, + 0xc3, 0x48, 0xbb, 0x16, 0x46, 0x01, 0x0b, 0xd0, 0xa7, 0xb5, 0xaa, 0x5a, 0xac, 0x4a, 0x3c, 0x7c, + 0xc9, 0x6d, 0xd7, 0xc2, 0x5e, 0xa7, 0xc6, 0x55, 0xd5, 0x0c, 0x55, 0xb5, 0x58, 0xd5, 0xd9, 0xe7, + 0x0c, 0x2b, 0x3a, 0x41, 0x27, 0xa8, 0x0b, 0x8d, 0xb7, 0x87, 0xbb, 0xe2, 0x4d, 0xbc, 0x88, 0x27, + 0x89, 0x74, 0xf6, 0x13, 0xbd, 0x8b, 0xb4, 0xe6, 0x05, 0xdc, 0xb6, 0x81, 0xe3, 0x76, 0x3d, 0x9f, + 0x44, 0x63, 0x6d, 0xec, 0x80, 0x30, 0xa7, 0x3e, 0x9a, 0xb0, 0xef, 0x6c, 0xfd, 0xb0, 0xaf, 0xa2, + 0xa1, 0xcf, 0xbc, 0x01, 0x99, 0xf8, 0xe0, 0x93, 0x47, 0x7d, 0x40, 0xdd, 0x2e, 0x19, 0x38, 0x13, + 0xdf, 0x7d, 0xfc, 0xb0, 0xef, 0x86, 0xcc, 0xeb, 0xd7, 0x3d, 0x9f, 0x51, 0x16, 0x65, 0x3f, 0xb2, + 0xff, 0x62, 0x01, 0x5c, 0x0a, 0xc3, 0xed, 0x28, 0xb8, 0x43, 0x5c, 0x86, 0xbe, 0x0c, 0x65, 0xfe, + 0x1f, 0x6d, 0x87, 0x39, 0xcb, 0xd6, 0x9a, 0x75, 0xbe, 0xba, 0xfe, 0xb1, 0x9a, 0x54, 0x5b, 0x33, + 0xd5, 0x6a, 0xbf, 0x72, 0xe9, 0xda, 0xe8, 0x42, 0xed, 0xd6, 0x6d, 0xfe, 0xfd, 0x0d, 0xc2, 0x9c, + 0x06, 0xba, 0xbf, 0xb7, 0x7a, 0x6a, 0x7f, 0x6f, 0x15, 0x34, 0x0d, 0x27, 0x5a, 0x51, 0x0f, 0x0a, + 0x34, 0x24, 0xee, 0x72, 0x4e, 0x68, 0xdf, 0xac, 0x3d, 0xf4, 0xea, 0xd5, 0xb4, 0xd9, 0xad, 0x90, + 0xb8, 0x8d, 0x79, 0x05, 0x5b, 0xe0, 0x6f, 0x58, 0x80, 0xd8, 0xef, 0x58, 0xb0, 0xa8, 0xc5, 0xae, + 0x7b, 0x94, 0xa1, 0x57, 0x27, 0xfe, 0xb0, 0x76, 0xbc, 0x3f, 0xe4, 0x5f, 0x8b, 0xff, 0x3b, 0xad, + 0x80, 0xca, 0x31, 0xc5, 0xf8, 0xbb, 0x3b, 0x50, 0xf4, 0x18, 0x19, 0xd0, 0xe5, 0xdc, 0x5a, 0xfe, + 0x7c, 0x75, 0xfd, 0xf2, 0x4c, 0x7e, 0xaf, 0xb1, 0xa0, 0x10, 0x8b, 0x9b, 0x5c, 0x37, 0x96, 0x10, + 0xf6, 0x3f, 0x73, 0xe6, 0xcf, 0xf1, 0xbf, 0x46, 0x17, 0xa0, 0x4a, 0x83, 0x61, 0xe4, 0x12, 0x4c, + 0xc2, 0x80, 0x2e, 0x5b, 0x6b, 0xf9, 0xf3, 0x95, 0xc6, 0xd2, 0xfe, 0xde, 0x6a, 0xb5, 0xa5, 0xc9, + 0xd8, 0x94, 0x41, 0xdf, 0xb5, 0x60, 0xbe, 0x4d, 0x28, 0xf3, 0x7c, 0x81, 0x1f, 0x5b, 0xfe, 0x85, + 0xe9, 0x2c, 0x8f, 0x89, 0x1b, 0x5a, 0x73, 0xe3, 0x49, 0xf5, 0x17, 0xf3, 0x06, 0x91, 0xe2, 0x14, + 0x38, 0x7a, 0x1e, 0xaa, 0x6d, 0x42, 0xdd, 0xc8, 0x0b, 0xf9, 0xfb, 0x72, 0x7e, 0xcd, 0x3a, 0x5f, + 0x69, 0x3c, 0xa1, 0x3e, 0xac, 0x6e, 0x68, 0x16, 0x36, 0xe5, 0x50, 0x0f, 0x8a, 0x51, 0xd0, 0x27, + 0x74, 0xb9, 0x20, 0x8c, 0xbf, 0x32, 0x85, 0xf1, 0xca, 0x9d, 0x38, 0xe8, 0x13, 0xed, 0x77, 0xfe, + 0x46, 0xb1, 0xc4, 0xb0, 0xff, 0x90, 0x87, 0xaa, 0xf1, 0x8b, 0x8f, 0x61, 0xcf, 0xf4, 0x53, 0x7b, + 0xe6, 0xda, 0x6c, 0x96, 0xe6, 0xb0, 0x4d, 0x83, 0x18, 0x94, 0x28, 0x73, 0xd8, 0x90, 0x0a, 0xf7, + 0x57, 0xd7, 0xaf, 0xcf, 0x08, 0x4f, 0xe8, 0x6c, 0x2c, 0x2a, 0xc4, 0x92, 0x7c, 0xc7, 0x0a, 0x0b, + 0xbd, 0x0e, 0x95, 0x20, 0xe4, 0xa9, 0x89, 0xaf, 0x7b, 0x41, 0x00, 0x6f, 0x4c, 0x01, 0x7c, 0x2b, + 0xd6, 0xd5, 0x58, 0xd8, 0xdf, 0x5b, 0xad, 0x24, 0xaf, 0x58, 0xa3, 0xd8, 0x2e, 0x3c, 0x69, 0xd8, + 0xd7, 0x0c, 0xfc, 0xb6, 0x27, 0x16, 0x74, 0x0d, 0x0a, 0x6c, 0x1c, 0x12, 0xb1, 0x98, 0x15, 0xed, + 0xa2, 0x9d, 0x71, 0x48, 0xb0, 0xe0, 0xa0, 0x8f, 0xc0, 0xdc, 0x80, 0x50, 0xea, 0x74, 0x88, 0x58, + 0x93, 0x4a, 0x63, 0x49, 0x09, 0xcd, 0xdd, 0x90, 0x64, 0x1c, 0xf3, 0xed, 0xd7, 0xe1, 0xe9, 0x83, + 0xf7, 0x03, 0xfa, 0x10, 0x94, 0x28, 0x89, 0x46, 0x24, 0x52, 0x40, 0xda, 0x33, 0x82, 0x8a, 0x15, + 0x17, 0xd5, 0xa1, 0xe2, 0x3b, 0x03, 0x42, 0x43, 0xc7, 0x8d, 0xe1, 0xce, 0x28, 0xd1, 0xca, 0xcd, + 0x98, 0x81, 0xb5, 0x8c, 0xfd, 0x57, 0x0b, 0x96, 0x0c, 0xcc, 0xc7, 0x90, 0xf6, 0x7a, 0xe9, 0xb4, + 0x77, 0x65, 0x36, 0x11, 0x73, 0x48, 0xde, 0xfb, 0x5d, 0x1e, 0xce, 0x98, 0x71, 0x25, 0x92, 0x19, + 0x5f, 0x92, 0x88, 0x84, 0xc1, 0x8b, 0xf8, 0xba, 0x72, 0x67, 0xb2, 0x24, 0x58, 0x92, 0x71, 0xcc, + 0xe7, 0xeb, 0x1b, 0x3a, 0xac, 0xab, 0x7c, 0x99, 0xac, 0xef, 0xb6, 0xc3, 0xba, 0x58, 0x70, 0x78, + 0x1a, 0x22, 0xfe, 0xc8, 0x8b, 0x02, 0x7f, 0x40, 0x7c, 0x96, 0x4d, 0x43, 0x97, 0x35, 0x0b, 0x9b, + 0x72, 0xe8, 0xf3, 0xb0, 0xc8, 0x9c, 0xa8, 0x43, 0x18, 0x26, 0x23, 0x8f, 0xc6, 0x81, 0x5c, 0x69, + 0x3c, 0xad, 0xbe, 0x5c, 0xdc, 0x49, 0x71, 0x71, 0x46, 0x1a, 0xfd, 0xda, 0x82, 0x67, 0xdc, 0x60, + 0x10, 0x06, 0x3e, 0xf1, 0xd9, 0xb6, 0x13, 0x39, 0x03, 0xc2, 0x48, 0x74, 0x6b, 0x44, 0xa2, 0xc8, + 0x6b, 0x13, 0xba, 0x5c, 0x14, 0xde, 0xbd, 0x31, 0x85, 0x77, 0x9b, 0x13, 0xda, 0x1b, 0xe7, 0x94, + 0x71, 0xcf, 0x34, 0x0f, 0x47, 0xc6, 0xff, 0xce, 0x2c, 0x5e, 0x75, 0x46, 0x4e, 0x7f, 0x48, 0xe8, + 0x15, 0x8f, 0xe7, 0xe0, 0x92, 0xae, 0x3a, 0x2f, 0x69, 0x32, 0x36, 0x65, 0xec, 0xdf, 0xe6, 0x52, + 0x21, 0xda, 0x8a, 0xf3, 0x8e, 0x58, 0x4b, 0x15, 0xa0, 0xb3, 0xca, 0x3b, 0x42, 0xa7, 0xb1, 0xbb, + 0x64, 0xf1, 0x53, 0x58, 0xe8, 0x5b, 0x96, 0x28, 0x39, 0xf1, 0xae, 0x54, 0x39, 0xf6, 0x11, 0x94, + 0x3f, 0xb3, 0x8a, 0xc5, 0x44, 0x6c, 0x42, 0xf3, 0x10, 0x0e, 0x65, 0xf5, 0x51, 0x11, 0x97, 0x84, + 0x70, 0x5c, 0x94, 0x62, 0xbe, 0xfd, 0xd3, 0x52, 0x7a, 0x0f, 0xc8, 0x1c, 0xfa, 0x43, 0x0b, 0x4e, + 0xf3, 0x85, 0x72, 0x22, 0x8f, 0x06, 0x3e, 0x26, 0x74, 0xd8, 0x67, 0xca, 0x99, 0x5b, 0x53, 0x06, + 0x8d, 0xa9, 0xb2, 0xb1, 0xac, 0xec, 0x3a, 0x9d, 0xe5, 0xe0, 0x09, 0x78, 0xc4, 0x60, 0xae, 0xeb, + 0x51, 0x16, 0x44, 0x63, 0x95, 0x1c, 0xa6, 0x69, 0xf9, 0x36, 0x48, 0xd8, 0x0f, 0xc6, 0x7c, 0xaf, + 0x6d, 0xfa, 0xbb, 0x81, 0xf6, 0xcf, 0x55, 0x89, 0x80, 0x63, 0x28, 0xf4, 0x35, 0x0b, 0x20, 0x8c, + 0x23, 0x95, 0x17, 0xb2, 0x47, 0xb0, 0x71, 0x92, 0x9a, 0x9d, 0x90, 0x28, 0x36, 0x40, 0x51, 0x00, + 0xa5, 0x2e, 0x71, 0xfa, 0xac, 0xab, 0xca, 0xd9, 0x0b, 0x53, 0xc0, 0x5f, 0x15, 0x8a, 0xb2, 0x25, + 0x54, 0x52, 0xb1, 0x82, 0x41, 0xdf, 0xb0, 0x60, 0x31, 0xa9, 0x6e, 0x5c, 0x96, 0x2c, 0x17, 0xa7, + 0xee, 0xb2, 0x6f, 0xa5, 0x14, 0x36, 0x10, 0x4f, 0x63, 0x69, 0x1a, 0xce, 0x80, 0xa2, 0xaf, 0x5b, + 0x00, 0x6e, 0x5c, 0x4d, 0x65, 0x3e, 0xa8, 0xae, 0xdf, 0x9a, 0xcd, 0x8e, 0x4a, 0xaa, 0xb4, 0x76, + 0x7f, 0x42, 0xa2, 0xd8, 0x80, 0xb5, 0xdf, 0xb5, 0xe0, 0x29, 0xe3, 0xc3, 0x97, 0x1d, 0xe6, 0x76, + 0x2f, 0x8f, 0x78, 0x9a, 0xde, 0x4a, 0xd5, 0xf7, 0x4f, 0x99, 0xf5, 0xfd, 0xfd, 0xbd, 0xd5, 0x0f, + 0x1f, 0x36, 0x46, 0xdd, 0xe5, 0x1a, 0x6a, 0x42, 0x85, 0xd1, 0x0a, 0xbc, 0x01, 0x55, 0xc3, 0x66, + 0x95, 0x3e, 0x66, 0x55, 0x00, 0x93, 0x9c, 0x61, 0x10, 0xb1, 0x89, 0x67, 0xff, 0x29, 0x07, 0x73, + 0xcd, 0xfe, 0x90, 0x32, 0x12, 0x1d, 0xbb, 0xa1, 0x58, 0x83, 0x02, 0x6f, 0x16, 0xb2, 0xf5, 0x8f, + 0xf7, 0x12, 0x58, 0x70, 0x50, 0x08, 0x25, 0x37, 0xf0, 0x77, 0xbd, 0x8e, 0x6a, 0x01, 0xaf, 0x4e, + 0xb3, 0x73, 0xa4, 0x75, 0x4d, 0xa1, 0x4f, 0xdb, 0x24, 0xdf, 0xb1, 0xc2, 0x41, 0xdf, 0xb7, 0x60, + 0xc9, 0x0d, 0x7c, 0x9f, 0xb8, 0x3a, 0x78, 0x0b, 0x53, 0xb7, 0xbb, 0xcd, 0xb4, 0xc6, 0xc6, 0xff, + 0x29, 0xf4, 0xa5, 0x0c, 0x03, 0x67, 0xb1, 0xed, 0x5f, 0xe5, 0x60, 0x21, 0x65, 0x39, 0x7a, 0x16, + 0xca, 0x43, 0x4a, 0x22, 0xe1, 0x39, 0xe9, 0xdf, 0xa4, 0x23, 0x7a, 0x51, 0xd1, 0x71, 0x22, 0xc1, + 0xa5, 0x43, 0x87, 0xd2, 0xbb, 0x41, 0xd4, 0x56, 0x7e, 0x4e, 0xa4, 0xb7, 0x15, 0x1d, 0x27, 0x12, + 0xbc, 0xdf, 0xb8, 0x4d, 0x9c, 0x88, 0x44, 0x3b, 0x41, 0x8f, 0x4c, 0x8c, 0x3d, 0x0d, 0xcd, 0xc2, + 0xa6, 0x9c, 0x70, 0x1a, 0xeb, 0xd3, 0x66, 0xdf, 0x23, 0x3e, 0x93, 0x66, 0xce, 0xc0, 0x69, 0x3b, + 0xd7, 0x5b, 0xa6, 0x46, 0xed, 0xb4, 0x0c, 0x03, 0x67, 0xb1, 0xed, 0x3f, 0x5a, 0x50, 0x55, 0x4e, + 0x7b, 0x0c, 0x4d, 0x67, 0x27, 0xdd, 0x74, 0x36, 0xa6, 0x8f, 0xd1, 0x43, 0x1a, 0xce, 0x5f, 0xe4, + 0x61, 0xa2, 0xd2, 0xa1, 0xd7, 0x78, 0x8e, 0xe3, 0x34, 0xd2, 0xbe, 0x14, 0x17, 0xd9, 0x8f, 0x1e, + 0xef, 0xef, 0x76, 0xbc, 0x01, 0x31, 0xd3, 0x57, 0xac, 0x05, 0x1b, 0x1a, 0xd1, 0x9b, 0x96, 0x06, + 0xd8, 0x09, 0x54, 0x5e, 0x99, 0x6d, 0x4b, 0x34, 0x61, 0xc2, 0x4e, 0x80, 0x0d, 0x4c, 0xf4, 0x99, + 0x64, 0x10, 0x2c, 0x8a, 0x80, 0xb4, 0xd3, 0xa3, 0xdb, 0xfb, 0xa9, 0x06, 0x20, 0x33, 0xce, 0x8d, + 0xa1, 0x12, 0x11, 0xd9, 0x62, 0xc5, 0x15, 0x60, 0x9a, 0x24, 0x82, 0x95, 0x2e, 0xb9, 0x8d, 0x93, + 0xf1, 0x27, 0x26, 0x53, 0xac, 0xd1, 0xec, 0xef, 0x59, 0x80, 0x26, 0xcb, 0x35, 0x1f, 0xa3, 0x92, + 0x26, 0x56, 0x6d, 0xe0, 0x44, 0x4f, 0x22, 0x8e, 0xb5, 0xcc, 0x31, 0xd2, 0xe4, 0x39, 0x28, 0x8a, + 0xa6, 0x56, 0x6d, 0xd8, 0x24, 0x7a, 0x44, 0xdb, 0x8b, 0x25, 0xcf, 0xfe, 0xbd, 0x05, 0xd9, 0x74, + 0x23, 0x32, 0xb5, 0xf4, 0x6c, 0x36, 0x53, 0xa7, 0xbd, 0x78, 0xfc, 0x39, 0x13, 0xbd, 0x0a, 0x55, + 0x87, 0x31, 0x32, 0x08, 0x99, 0x08, 0xc8, 0xfc, 0x89, 0x03, 0x72, 0x91, 0x47, 0xc2, 0x8d, 0xa0, + 0xed, 0xed, 0x7a, 0x22, 0x18, 0x4d, 0x75, 0xf6, 0x7b, 0x79, 0x58, 0x4c, 0x37, 0x5f, 0x68, 0x08, + 0x25, 0xd1, 0xec, 0xc8, 0x63, 0xa6, 0x99, 0x77, 0x57, 0x89, 0x4b, 0x04, 0x89, 0x62, 0x05, 0xc6, + 0x13, 0x6b, 0x14, 0x4f, 0x57, 0x99, 0xc4, 0x9a, 0xcc, 0x55, 0x89, 0xc4, 0x91, 0x13, 0x55, 0xfe, + 0xbf, 0x73, 0xa2, 0x7a, 0x0d, 0xa0, 0x2d, 0xbc, 0x2d, 0xd6, 0xb2, 0xf0, 0xf0, 0xc9, 0x65, 0x23, + 0xd1, 0x82, 0x0d, 0x8d, 0xe8, 0x2c, 0xe4, 0xbc, 0xb6, 0xd8, 0xd5, 0xf9, 0x06, 0x28, 0xd9, 0xdc, + 0xe6, 0x06, 0xce, 0x79, 0x6d, 0x9b, 0xc2, 0xbc, 0xd9, 0x6d, 0x1e, 0x3b, 0x56, 0x3f, 0x0b, 0x0b, + 0xf2, 0x69, 0x83, 0x30, 0xc7, 0xeb, 0x53, 0xb5, 0x3a, 0x4f, 0x29, 0xf1, 0x85, 0x96, 0xc9, 0xc4, + 0x69, 0x59, 0xfb, 0x27, 0x39, 0x80, 0xab, 0x41, 0xd0, 0x53, 0x98, 0xf1, 0xd6, 0xb3, 0x0e, 0xdd, + 0x7a, 0x6b, 0x50, 0xe8, 0x79, 0x7e, 0x3b, 0xbb, 0x39, 0xb7, 0x3c, 0xbf, 0x8d, 0x05, 0x07, 0xad, + 0x03, 0x38, 0xa1, 0xf7, 0x12, 0x89, 0xa8, 0x3e, 0x49, 0x4c, 0xfc, 0x72, 0x69, 0x7b, 0x53, 0x71, + 0xb0, 0x21, 0x85, 0x9e, 0x55, 0x9d, 0xa1, 0x1c, 0xdb, 0x97, 0x33, 0x9d, 0x61, 0x99, 0x5b, 0x68, + 0xb4, 0x7e, 0x17, 0x33, 0xf9, 0x71, 0x6d, 0x22, 0x3f, 0xea, 0x4e, 0x79, 0xbb, 0xeb, 0x50, 0x72, + 0xd0, 0xbe, 0x2e, 0x1d, 0x71, 0x7e, 0xd4, 0x82, 0xf2, 0xb5, 0x97, 0x77, 0x64, 0xbd, 0xb7, 0x21, + 0xef, 0x39, 0x32, 0x79, 0xe5, 0x75, 0xd8, 0x6f, 0x52, 0x3a, 0x14, 0x2b, 0xcc, 0x99, 0xe8, 0x1c, + 0xe4, 0xc9, 0xbd, 0x50, 0xf8, 0x25, 0xaf, 0x13, 0xdc, 0xe5, 0x7b, 0xa1, 0x17, 0x11, 0xca, 0x85, + 0xc8, 0xbd, 0xd0, 0xfe, 0xbb, 0x05, 0xfa, 0x48, 0x0c, 0xed, 0x42, 0x81, 0x8e, 0x7d, 0x57, 0x15, + 0xb1, 0x69, 0xd2, 0x74, 0x6b, 0xec, 0xbb, 0xfa, 0xe4, 0xad, 0x2c, 0x0e, 0x16, 0xc7, 0xbe, 0x8b, + 0x85, 0x7e, 0x34, 0x82, 0x72, 0x14, 0xf4, 0xfb, 0xb7, 0x1d, 0xb7, 0x37, 0x83, 0x7a, 0x86, 0x95, + 0x2a, 0x8d, 0x37, 0x2f, 0x92, 0x80, 0x22, 0xe3, 0x04, 0xcb, 0xfe, 0x65, 0x11, 0x32, 0x23, 0x0b, + 0x1a, 0x9a, 0xa7, 0x8d, 0xd6, 0x0c, 0x4f, 0x1b, 0x13, 0x8f, 0x1f, 0x74, 0xe2, 0x88, 0x9e, 0x87, + 0x62, 0xc8, 0x03, 0x41, 0x85, 0xed, 0x6a, 0x5c, 0x30, 0x44, 0x74, 0x1c, 0x10, 0x2f, 0x52, 0xda, + 0x0c, 0x97, 0xfc, 0x11, 0x65, 0xe0, 0xab, 0x00, 0xdc, 0xd7, 0x6a, 0xf6, 0x97, 0x99, 0xe3, 0xe6, + 0xac, 0x56, 0x54, 0x8d, 0xff, 0xa2, 0x52, 0xb4, 0x12, 0x14, 0x6c, 0x20, 0xa2, 0xef, 0x58, 0xb0, + 0x18, 0x3b, 0x5e, 0x19, 0x51, 0x7c, 0x24, 0x46, 0x88, 0x41, 0x14, 0xa7, 0x90, 0x70, 0x06, 0x19, + 0x7d, 0x11, 0x2a, 0x94, 0x39, 0x91, 0xac, 0x88, 0xa5, 0x13, 0x67, 0xd1, 0x64, 0x2d, 0x5b, 0xb1, + 0x12, 0xac, 0xf5, 0xa1, 0x57, 0x00, 0x76, 0x3d, 0xdf, 0xa3, 0x5d, 0xa1, 0x7d, 0xee, 0xe1, 0xea, + 0xed, 0x95, 0x44, 0x03, 0x36, 0xb4, 0xd9, 0xdf, 0xcc, 0x41, 0xd5, 0xb8, 0x88, 0x38, 0x46, 0x3e, + 0xcc, 0x5c, 0x9c, 0xe4, 0x8e, 0x79, 0x71, 0x72, 0x1e, 0xca, 0x61, 0xd0, 0xf7, 0x5c, 0x4f, 0xd5, + 0xc2, 0x8a, 0xdc, 0x44, 0xdb, 0x8a, 0x86, 0x13, 0x2e, 0x62, 0x50, 0xb9, 0x73, 0x97, 0x89, 0x3c, + 0x14, 0x5f, 0xb3, 0x34, 0xa7, 0x58, 0xd2, 0x38, 0xa7, 0x69, 0x27, 0xc7, 0x14, 0x8a, 0x35, 0x90, + 0xfd, 0xe7, 0x1c, 0x80, 0xb8, 0xa7, 0xf2, 0xc4, 0xb1, 0xce, 0x1a, 0x14, 0x22, 0x12, 0x06, 0x59, + 0x3f, 0x70, 0x09, 0x2c, 0x38, 0xa9, 0x29, 0x2d, 0x77, 0xa2, 0x29, 0x2d, 0x7f, 0xe4, 0x94, 0xc6, + 0x2b, 0x1c, 0xed, 0x6e, 0x47, 0xde, 0xc8, 0x61, 0x64, 0x8b, 0x8c, 0x55, 0x99, 0xd0, 0x15, 0xae, + 0x75, 0x55, 0x33, 0x71, 0x5a, 0xf6, 0xc0, 0x01, 0xb7, 0xf8, 0x1f, 0x1c, 0x70, 0xdf, 0xb1, 0x60, + 0x51, 0x7b, 0xf6, 0x7f, 0xeb, 0x6a, 0x54, 0xdb, 0x7d, 0xc8, 0xc4, 0xf6, 0x0f, 0x0b, 0x96, 0xe2, + 0xd9, 0x40, 0xb5, 0x18, 0x33, 0xe9, 0x29, 0x52, 0x57, 0x31, 0xf9, 0xa3, 0xaf, 0x62, 0xcc, 0xcc, + 0x5d, 0x38, 0x22, 0x73, 0x7f, 0x2e, 0xd3, 0x4d, 0x7c, 0x60, 0xa2, 0x9b, 0x40, 0xc9, 0x14, 0x34, + 0xf6, 0xdd, 0x74, 0xf7, 0x65, 0xff, 0xdc, 0x82, 0xf9, 0x98, 0x7d, 0x33, 0x68, 0x8b, 0xd9, 0x84, + 0x8a, 0x20, 0xb3, 0xd2, 0xb3, 0x89, 0x0c, 0x07, 0xc9, 0x43, 0x43, 0x28, 0xbb, 0x5d, 0xaf, 0xdf, + 0x8e, 0x88, 0xaf, 0x96, 0xe5, 0x85, 0x19, 0x0c, 0x69, 0x1c, 0x5f, 0x87, 0x42, 0x53, 0x01, 0xe0, + 0x04, 0xca, 0xfe, 0x4d, 0x1e, 0x16, 0x52, 0x13, 0x1d, 0x4f, 0x5f, 0xf2, 0x2e, 0xa4, 0x65, 0xd8, + 0x9c, 0xa4, 0xaf, 0x1d, 0xcd, 0xc2, 0xa6, 0x1c, 0x5f, 0x8f, 0xbe, 0x37, 0x92, 0x3a, 0xb2, 0x57, + 0x63, 0xd7, 0x63, 0x06, 0xd6, 0x32, 0xc6, 0x48, 0x9b, 0x3f, 0xf1, 0x48, 0xfb, 0x23, 0x0b, 0x90, + 0xf8, 0x05, 0xae, 0x39, 0x99, 0x3c, 0x55, 0x2e, 0x9c, 0x99, 0xdf, 0xce, 0x2a, 0x8b, 0x50, 0x73, + 0x02, 0x0a, 0x1f, 0x00, 0x6f, 0x9c, 0x32, 0x17, 0x1f, 0xcb, 0x29, 0xb3, 0xfd, 0x15, 0x38, 0x33, + 0xd1, 0x7a, 0xa9, 0x81, 0xc2, 0x3a, 0x68, 0xa0, 0xe0, 0x91, 0x18, 0x46, 0x43, 0x5f, 0x2e, 0x50, + 0x59, 0x47, 0xe2, 0x36, 0x27, 0x62, 0xc9, 0xe3, 0x53, 0x46, 0x3b, 0x1a, 0xe3, 0xa1, 0xec, 0xd4, + 0xcb, 0x1a, 0x7d, 0x43, 0x50, 0xb1, 0xe2, 0xda, 0xdf, 0xce, 0xc1, 0x42, 0xaa, 0x1d, 0x48, 0x0d, + 0x84, 0xd6, 0x91, 0x03, 0xe1, 0x2c, 0x8d, 0x41, 0x6f, 0xc0, 0x3c, 0x15, 0x5b, 0x31, 0x72, 0x18, + 0xe9, 0x8c, 0x67, 0x70, 0xce, 0xdf, 0x32, 0xd4, 0x35, 0x4e, 0xef, 0xef, 0xad, 0xce, 0x9b, 0x14, + 0x9c, 0x82, 0xb3, 0x7f, 0x96, 0x83, 0x27, 0x0e, 0x68, 0x8d, 0xd0, 0x5d, 0xf3, 0xec, 0x45, 0x0e, + 0xe7, 0xd7, 0x66, 0x10, 0x9e, 0x2a, 0x91, 0xca, 0x0b, 0xf5, 0x83, 0x4e, 0x5e, 0x4e, 0x38, 0x9b, + 0xef, 0x42, 0xb1, 0x1b, 0x04, 0xbd, 0x78, 0x08, 0x9f, 0xa6, 0x20, 0xe8, 0xd1, 0xb1, 0x51, 0xe1, + 0xab, 0xc9, 0xdf, 0x29, 0x96, 0xea, 0xed, 0xf7, 0x2c, 0x48, 0x79, 0x11, 0x0d, 0xa0, 0xc8, 0xb5, + 0x8c, 0x67, 0x70, 0xcf, 0x68, 0xea, 0xbd, 0xc4, 0x75, 0x4a, 0x7c, 0xf1, 0x88, 0x25, 0x0a, 0xf2, + 0xa0, 0xc0, 0x0d, 0x51, 0x23, 0xcf, 0xd6, 0x8c, 0xd0, 0xf8, 0x2f, 0xca, 0x09, 0x8b, 0x3f, 0x61, + 0x01, 0x61, 0x5f, 0x84, 0x33, 0x13, 0x16, 0xf1, 0x90, 0xdf, 0x0d, 0xe2, 0x6b, 0x55, 0x23, 0xe4, + 0xaf, 0x70, 0x22, 0x96, 0x3c, 0x5e, 0x3f, 0x4e, 0x67, 0xd5, 0xa3, 0x1f, 0x5b, 0x70, 0x86, 0x66, + 0xf5, 0x3d, 0x12, 0xaf, 0xfd, 0xbf, 0x32, 0x6a, 0xd2, 0x7c, 0x3c, 0x69, 0x01, 0x5f, 0xd1, 0xec, + 0x61, 0x34, 0x8f, 0x3d, 0xcf, 0xa7, 0xc4, 0x1d, 0x46, 0xf1, 0x8f, 0xea, 0x01, 0x59, 0xd1, 0x71, + 0x22, 0x81, 0xd6, 0x01, 0xe4, 0x65, 0xc8, 0x4d, 0xdd, 0x28, 0x26, 0x87, 0x03, 0xad, 0x84, 0x83, + 0x0d, 0x29, 0xde, 0x2b, 0xbb, 0x24, 0x62, 0x1b, 0xbc, 0x3d, 0xe2, 0x79, 0x61, 0x5e, 0xf6, 0xca, + 0x4d, 0x45, 0xc3, 0x09, 0x17, 0x7d, 0x10, 0xe6, 0x7a, 0x64, 0x2c, 0x04, 0x0b, 0x42, 0xb0, 0xca, + 0x2b, 0xfe, 0x96, 0x24, 0xe1, 0x98, 0x87, 0x6c, 0x28, 0xb9, 0x8e, 0x90, 0x2a, 0x0a, 0x29, 0x10, + 0xf7, 0x22, 0x97, 0x84, 0x90, 0xe2, 0x34, 0x6a, 0xf7, 0x1f, 0xac, 0x9c, 0x7a, 0xeb, 0xc1, 0xca, + 0xa9, 0xb7, 0x1f, 0xac, 0x9c, 0x7a, 0x73, 0x7f, 0xc5, 0xba, 0xbf, 0xbf, 0x62, 0xbd, 0xb5, 0xbf, + 0x62, 0xbd, 0xbd, 0xbf, 0x62, 0xfd, 0x6d, 0x7f, 0xc5, 0xfa, 0xc1, 0xbb, 0x2b, 0xa7, 0x5e, 0x29, + 0xc7, 0xae, 0xfd, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x38, 0x84, 0xaf, 0x0d, 0x59, 0x29, 0x00, + 0x00, } diff --git a/pkg/apis/application/v1alpha1/generated.proto b/pkg/apis/application/v1alpha1/generated.proto index 509ac647d03e4..16b3057f006cb 100644 --- a/pkg/apis/application/v1alpha1/generated.proto +++ b/pkg/apis/application/v1alpha1/generated.proto @@ -41,6 +41,8 @@ message AppProjectSpec { // Description contains optional project description optional string description = 3; + + repeated ProjectRole roles = 4; } // Application is a definition of Application resource. @@ -252,6 +254,13 @@ message HookStatus { optional string message = 6; } +// JWTToken holds the issuedAt and expiresAt values of a token +message JWTToken { + optional int64 iat = 1; + + optional int64 exp = 2; +} + // Operation contains requested operation parameters. message Operation { optional SyncOperation sync = 1; @@ -283,6 +292,18 @@ message OperationState { optional k8s.io.apimachinery.pkg.apis.meta.v1.Time finishedAt = 7; } +// ProjectRole represents a role that has access to a project +message ProjectRole { + optional string name = 1; + + optional string description = 2; + + // Policies Stores a list of casbin formated strings that define access policies for the role in the project. + repeated string policies = 3; + + repeated JWTToken jwtTokens = 4; +} + // Repository is a Git repository holding application configurations message Repository { optional string repo = 1; diff --git a/pkg/apis/application/v1alpha1/types.go b/pkg/apis/application/v1alpha1/types.go index b26efaf46b557..7d78659d42380 100644 --- a/pkg/apis/application/v1alpha1/types.go +++ b/pkg/apis/application/v1alpha1/types.go @@ -443,6 +443,15 @@ type AppProject struct { Spec AppProjectSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } +// ProjectPoliciesString returns Casbin formated string of a project's polcies for each role +func (proj *AppProject) ProjectPoliciesString() string { + var policies []string + for _, role := range proj.Spec.Roles { + policies = append(policies, role.Policies...) + } + return strings.Join(policies, "\n") +} + // AppProjectSpec represents type AppProjectSpec struct { // SourceRepos contains list of git repository URLs which can be used for deployment @@ -453,6 +462,23 @@ type AppProjectSpec struct { // Description contains optional project description Description string `json:"description,omitempty" protobuf:"bytes,3,opt,name=description"` + + Roles []ProjectRole `json:"roles,omitempty" protobuf:"bytes,4,rep,name=roles"` +} + +// ProjectRole represents a role that has access to a project +type ProjectRole struct { + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + Description string `json:"description" protobuf:"bytes,2,opt,name=description"` + // Policies Stores a list of casbin formated strings that define access policies for the role in the project. + Policies []string `json:"policies" protobuf:"bytes,3,rep,name=policies"` + JWTTokens []JWTToken `json:"jwtTokens" protobuf:"bytes,4,rep,name=jwtTokens"` +} + +// JWTToken holds the issuedAt and expiresAt values of a token +type JWTToken struct { + IssuedAt int64 `json:"iat,omitempty" protobuf:"int64,1,opt,name=iat"` + ExpiresAt int64 `json:"exp,omitempty" protobuf:"int64,2,opt,name=exp"` } func GetDefaultProject(namespace string) AppProject { diff --git a/pkg/apis/application/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/application/v1alpha1/zz_generated.deepcopy.go index 8676cf022b16e..b3f99492c0053 100644 --- a/pkg/apis/application/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/application/v1alpha1/zz_generated.deepcopy.go @@ -82,6 +82,13 @@ func (in *AppProjectSpec) DeepCopyInto(out *AppProjectSpec) { *out = make([]ApplicationDestination, len(*in)) copy(*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]) + } + } return } @@ -486,6 +493,22 @@ func (in *HookStatus) DeepCopy() *HookStatus { 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 + return +} + +// 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 *Operation) DeepCopyInto(out *Operation) { *out = *in @@ -565,6 +588,32 @@ func (in *OperationState) DeepCopy() *OperationState { 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) + } + return +} + +// 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 +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Repository) DeepCopyInto(out *Repository) { *out = *in diff --git a/server/application/application_test.go b/server/application/application_test.go index 885689dadb640..65eb8425b7c0b 100644 --- a/server/application/application_test.go +++ b/server/application/application_test.go @@ -78,7 +78,9 @@ func newTestAppServer() ApplicationServiceServer { enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil) enforcer.SetBuiltinPolicy(test.BuiltinPolicy) enforcer.SetDefaultRole("role:admin") - + enforcer.SetClaimsEnforcerFunc(func(rvals ...interface{}) bool { + return true + }) db := db.NewDB(testNamespace, kubeclientset) ctx := context.Background() _, err := db.CreateRepository(ctx, fakeRepo()) diff --git a/server/project/project.go b/server/project/project.go index d23c07e179866..f42078a63be2b 100644 --- a/server/project/project.go +++ b/server/project/project.go @@ -3,9 +3,15 @@ package project import ( "context" "fmt" - "strings" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/client-go/kubernetes" + "github.com/argoproj/argo-cd/common" "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned" @@ -13,14 +19,15 @@ import ( "github.com/argoproj/argo-cd/util/argo" "github.com/argoproj/argo-cd/util/git" "github.com/argoproj/argo-cd/util/grpc" + projectutil "github.com/argoproj/argo-cd/util/project" "github.com/argoproj/argo-cd/util/rbac" "github.com/argoproj/argo-cd/util/session" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/client-go/kubernetes" + jwt "github.com/dgrijalva/jwt-go" +) + +const ( + // JWTTokenSubFormat format of the JWT token subject that ArgoCD vends out. + JWTTokenSubFormat = "proj:%s:%s" ) // Server provides a Project service @@ -31,12 +38,99 @@ type Server struct { kubeclientset kubernetes.Interface auditLogger *argo.AuditLogger projectLock *util.KeyLock + sessionMgr *session.SessionManager } // NewServer returns a new instance of the Project service -func NewServer(ns string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface, enf *rbac.Enforcer, projectLock *util.KeyLock) *Server { +func NewServer(ns string, kubeclientset kubernetes.Interface, appclientset appclientset.Interface, enf *rbac.Enforcer, projectLock *util.KeyLock, sessionMgr *session.SessionManager) *Server { auditLogger := argo.NewAuditLogger(ns, kubeclientset, "argocd-server") - return &Server{enf: enf, appclientset: appclientset, kubeclientset: kubeclientset, ns: ns, projectLock: projectLock, auditLogger: auditLogger} + return &Server{enf: enf, appclientset: appclientset, kubeclientset: kubeclientset, ns: ns, projectLock: projectLock, auditLogger: auditLogger, sessionMgr: sessionMgr} +} + +// CreateToken creates a new token to access a project +func (s *Server) CreateToken(ctx context.Context, q *ProjectTokenCreateRequest) (*ProjectTokenResponse, error) { + if !s.enf.EnforceClaims(ctx.Value("claims"), "projects", "update", q.Project) { + return nil, grpc.ErrPermissionDenied + } + project, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(q.Project, metav1.GetOptions{}) + if err != nil { + return nil, err + } + err = validateProject(project) + if err != nil { + return nil, err + } + + s.projectLock.Lock(q.Project) + defer s.projectLock.Unlock(q.Project) + + index, err := projectutil.GetRoleIndexByName(project, q.Role) + if err != nil { + return nil, status.Errorf(codes.NotFound, "project '%s' does not have role '%s'", q.Project, q.Role) + } + + tokenName := fmt.Sprintf(JWTTokenSubFormat, q.Project, q.Role) + jwtToken, err := s.sessionMgr.Create(tokenName, q.ExpiresIn) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + parser := &jwt.Parser{ + SkipClaimsValidation: true, + } + claims := jwt.StandardClaims{} + _, _, err = parser.ParseUnverified(jwtToken, &claims) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + issuedAt := claims.IssuedAt + expiresAt := claims.ExpiresAt + + project.Spec.Roles[index].JWTTokens = append(project.Spec.Roles[index].JWTTokens, v1alpha1.JWTToken{IssuedAt: issuedAt, ExpiresAt: expiresAt}) + _, err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(project) + if err != nil { + return nil, err + } + s.logEvent(project, ctx, argo.EventReasonResourceCreated, "create token") + return &ProjectTokenResponse{Token: jwtToken}, nil + +} + +// DeleteToken deletes a token in a project +func (s *Server) DeleteToken(ctx context.Context, q *ProjectTokenDeleteRequest) (*EmptyResponse, error) { + if !s.enf.EnforceClaims(ctx.Value("claims"), "projects", "delete", q.Project) { + return nil, grpc.ErrPermissionDenied + } + project, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(q.Project, metav1.GetOptions{}) + if err != nil { + return nil, err + } + err = validateProject(project) + if err != nil { + return nil, err + } + + s.projectLock.Lock(q.Project) + defer s.projectLock.Unlock(q.Project) + + roleIndex, err := projectutil.GetRoleIndexByName(project, q.Role) + if err != nil { + return &EmptyResponse{}, nil + } + if project.Spec.Roles[roleIndex].JWTTokens == nil { + return &EmptyResponse{}, nil + } + jwtTokenIndex, err := projectutil.GetJWTTokenIndexByIssuedAt(project, roleIndex, q.Iat) + if err != nil { + return &EmptyResponse{}, nil + } + project.Spec.Roles[roleIndex].JWTTokens[jwtTokenIndex] = project.Spec.Roles[roleIndex].JWTTokens[len(project.Spec.Roles[roleIndex].JWTTokens)-1] + project.Spec.Roles[roleIndex].JWTTokens = project.Spec.Roles[roleIndex].JWTTokens[:len(project.Spec.Roles[roleIndex].JWTTokens)-1] + _, err = s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(project) + if err != nil { + return nil, err + } + s.logEvent(project, ctx, argo.EventReasonResourceDeleted, "deleted token") + return &EmptyResponse{}, nil } // Create a new project. @@ -123,6 +217,58 @@ func getRemovedSources(oldProj, newProj *v1alpha1.AppProject) map[string]bool { return removed } +func validateJWTToken(proj string, token string, policy string) error { + err := validatePolicy(proj, policy) + if err != nil { + return err + } + policyComponents := strings.Split(policy, ",") + if strings.Trim(policyComponents[2], " ") != "applications" { + return status.Errorf(codes.InvalidArgument, "incorrect format for '%s' as JWT tokens can only access applications", policy) + } + roleComponents := strings.Split(strings.Trim(policyComponents[1], " "), ":") + if len(roleComponents) != 3 { + return status.Errorf(codes.InvalidArgument, "incorrect number of role arguments for '%s' policy", policy) + } + if roleComponents[0] != "proj" { + return status.Errorf(codes.InvalidArgument, "incorrect policy format for '%s' as role should start with 'proj:'", policy) + } + if roleComponents[1] != proj { + return status.Errorf(codes.InvalidArgument, "incorrect policy format for '%s' as policy can't grant access to other projects", policy) + } + if roleComponents[2] != token { + return status.Errorf(codes.InvalidArgument, "incorrect policy format for '%s' as policy can't grant access to other roles", policy) + } + return nil +} + +func validatePolicy(proj string, policy string) error { + policyComponents := strings.Split(policy, ",") + if len(policyComponents) != 6 { + return status.Errorf(codes.InvalidArgument, "incorrect number of policy arguments for '%s'", policy) + } + if strings.Trim(policyComponents[0], " ") != "p" { + return status.Errorf(codes.InvalidArgument, "policies can only use the policy format: '%s'", policy) + } + if len(strings.Trim(policyComponents[1], " ")) <= 0 { + return status.Errorf(codes.InvalidArgument, "incorrect policy format for '%s' as subject must be longer than 0 characters:", policy) + } + if len(strings.Trim(policyComponents[2], " ")) <= 0 { + return status.Errorf(codes.InvalidArgument, "incorrect policy format for '%s' as object must be longer than 0 characters:", policy) + } + if len(strings.Trim(policyComponents[3], " ")) <= 0 { + return status.Errorf(codes.InvalidArgument, "incorrect policy format for '%s' as action must be longer than 0 characters:", policy) + } + if !strings.HasPrefix(strings.Trim(policyComponents[4], " "), proj) { + return status.Errorf(codes.InvalidArgument, "incorrect policy format for '%s' as policies can't grant access to other projects", policy) + } + effect := strings.Trim(policyComponents[5], " ") + if effect != "allow" && effect != "deny" { + return status.Errorf(codes.InvalidArgument, "incorrect policy format for '%s' as effect can only have value 'allow' or 'deny'", policy) + } + return nil +} + func validateProject(p *v1alpha1.AppProject) error { destKeys := make(map[string]bool) for _, dest := range p.Spec.Destinations { @@ -143,6 +289,34 @@ func validateProject(p *v1alpha1.AppProject) error { return status.Errorf(codes.InvalidArgument, "source repository %s should not be listed more than once.", src) } } + + roleNames := make(map[string]bool) + for _, role := range p.Spec.Roles { + existingPolicies := make(map[string]bool) + for _, policy := range role.Policies { + var err error + if role.JWTTokens != nil { + err = validateJWTToken(p.Name, role.Name, policy) + } else { + err = validatePolicy(p.Name, policy) + } + if err != nil { + return err + } + if _, ok := existingPolicies[policy]; !ok { + existingPolicies[policy] = true + } else { + return status.Errorf(codes.AlreadyExists, "policy '%s' already exists for role '%s'", policy, role.Name) + } + } + if _, ok := roleNames[role.Name]; !ok { + roleNames[role.Name] = true + } else { + return status.Errorf(codes.AlreadyExists, "can't have duplicate roles: role '%s' already exists", role) + } + + } + return nil } diff --git a/server/project/project.pb.go b/server/project/project.pb.go index 6f12fe0ac2d03..f5009b7085afe 100644 --- a/server/project/project.pb.go +++ b/server/project/project.pb.go @@ -13,6 +13,9 @@ It has these top-level messages: ProjectCreateRequest + ProjectTokenDeleteRequest + ProjectTokenCreateRequest + ProjectTokenResponse ProjectQuery ProjectUpdateRequest EmptyResponse @@ -61,6 +64,98 @@ func (m *ProjectCreateRequest) GetProject() *github_com_argoproj_argo_cd_pkg_api return nil } +// ProjectTokenCreateRequest defines project token deletion parameters. +type ProjectTokenDeleteRequest struct { + Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` + Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` + Iat int64 `protobuf:"varint,3,opt,name=iat,proto3" json:"iat,omitempty"` +} + +func (m *ProjectTokenDeleteRequest) Reset() { *m = ProjectTokenDeleteRequest{} } +func (m *ProjectTokenDeleteRequest) String() string { return proto.CompactTextString(m) } +func (*ProjectTokenDeleteRequest) ProtoMessage() {} +func (*ProjectTokenDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptorProject, []int{1} } + +func (m *ProjectTokenDeleteRequest) GetProject() string { + if m != nil { + return m.Project + } + return "" +} + +func (m *ProjectTokenDeleteRequest) GetRole() string { + if m != nil { + return m.Role + } + return "" +} + +func (m *ProjectTokenDeleteRequest) GetIat() int64 { + if m != nil { + return m.Iat + } + return 0 +} + +// ProjectTokenCreateRequest defines project token creation parameters. +type ProjectTokenCreateRequest struct { + Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + Role string `protobuf:"bytes,3,opt,name=role,proto3" json:"role,omitempty"` + // expiresIn represents a duration in seconds + ExpiresIn int64 `protobuf:"varint,4,opt,name=expiresIn,proto3" json:"expiresIn,omitempty"` +} + +func (m *ProjectTokenCreateRequest) Reset() { *m = ProjectTokenCreateRequest{} } +func (m *ProjectTokenCreateRequest) String() string { return proto.CompactTextString(m) } +func (*ProjectTokenCreateRequest) ProtoMessage() {} +func (*ProjectTokenCreateRequest) Descriptor() ([]byte, []int) { return fileDescriptorProject, []int{2} } + +func (m *ProjectTokenCreateRequest) GetProject() string { + if m != nil { + return m.Project + } + return "" +} + +func (m *ProjectTokenCreateRequest) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *ProjectTokenCreateRequest) GetRole() string { + if m != nil { + return m.Role + } + return "" +} + +func (m *ProjectTokenCreateRequest) GetExpiresIn() int64 { + if m != nil { + return m.ExpiresIn + } + return 0 +} + +// ProjectTokenResponse wraps the created token or returns an empty string if deleted. +type ProjectTokenResponse struct { + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` +} + +func (m *ProjectTokenResponse) Reset() { *m = ProjectTokenResponse{} } +func (m *ProjectTokenResponse) String() string { return proto.CompactTextString(m) } +func (*ProjectTokenResponse) ProtoMessage() {} +func (*ProjectTokenResponse) Descriptor() ([]byte, []int) { return fileDescriptorProject, []int{3} } + +func (m *ProjectTokenResponse) GetToken() string { + if m != nil { + return m.Token + } + return "" +} + // ProjectQuery is a query for Project resources type ProjectQuery struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -69,7 +164,7 @@ type ProjectQuery struct { func (m *ProjectQuery) Reset() { *m = ProjectQuery{} } func (m *ProjectQuery) String() string { return proto.CompactTextString(m) } func (*ProjectQuery) ProtoMessage() {} -func (*ProjectQuery) Descriptor() ([]byte, []int) { return fileDescriptorProject, []int{1} } +func (*ProjectQuery) Descriptor() ([]byte, []int) { return fileDescriptorProject, []int{4} } func (m *ProjectQuery) GetName() string { if m != nil { @@ -85,7 +180,7 @@ type ProjectUpdateRequest struct { func (m *ProjectUpdateRequest) Reset() { *m = ProjectUpdateRequest{} } func (m *ProjectUpdateRequest) String() string { return proto.CompactTextString(m) } func (*ProjectUpdateRequest) ProtoMessage() {} -func (*ProjectUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptorProject, []int{2} } +func (*ProjectUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptorProject, []int{5} } func (m *ProjectUpdateRequest) GetProject() *github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.AppProject { if m != nil { @@ -100,10 +195,13 @@ type EmptyResponse struct { func (m *EmptyResponse) Reset() { *m = EmptyResponse{} } func (m *EmptyResponse) String() string { return proto.CompactTextString(m) } func (*EmptyResponse) ProtoMessage() {} -func (*EmptyResponse) Descriptor() ([]byte, []int) { return fileDescriptorProject, []int{3} } +func (*EmptyResponse) Descriptor() ([]byte, []int) { return fileDescriptorProject, []int{6} } func init() { proto.RegisterType((*ProjectCreateRequest)(nil), "project.ProjectCreateRequest") + proto.RegisterType((*ProjectTokenDeleteRequest)(nil), "project.ProjectTokenDeleteRequest") + proto.RegisterType((*ProjectTokenCreateRequest)(nil), "project.ProjectTokenCreateRequest") + proto.RegisterType((*ProjectTokenResponse)(nil), "project.ProjectTokenResponse") proto.RegisterType((*ProjectQuery)(nil), "project.ProjectQuery") proto.RegisterType((*ProjectUpdateRequest)(nil), "project.ProjectUpdateRequest") proto.RegisterType((*EmptyResponse)(nil), "project.EmptyResponse") @@ -120,6 +218,10 @@ const _ = grpc.SupportPackageIsVersion4 // Client API for ProjectService service type ProjectServiceClient interface { + // Create a new project token. + CreateToken(ctx context.Context, in *ProjectTokenCreateRequest, opts ...grpc.CallOption) (*ProjectTokenResponse, error) + // Delete a new project token. + DeleteToken(ctx context.Context, in *ProjectTokenDeleteRequest, opts ...grpc.CallOption) (*EmptyResponse, error) // Create a new project. Create(ctx context.Context, in *ProjectCreateRequest, opts ...grpc.CallOption) (*github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.AppProject, error) // List returns list of projects @@ -142,6 +244,24 @@ func NewProjectServiceClient(cc *grpc.ClientConn) ProjectServiceClient { return &projectServiceClient{cc} } +func (c *projectServiceClient) CreateToken(ctx context.Context, in *ProjectTokenCreateRequest, opts ...grpc.CallOption) (*ProjectTokenResponse, error) { + out := new(ProjectTokenResponse) + err := grpc.Invoke(ctx, "/project.ProjectService/CreateToken", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *projectServiceClient) DeleteToken(ctx context.Context, in *ProjectTokenDeleteRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { + out := new(EmptyResponse) + err := grpc.Invoke(ctx, "/project.ProjectService/DeleteToken", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *projectServiceClient) Create(ctx context.Context, in *ProjectCreateRequest, opts ...grpc.CallOption) (*github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.AppProject, error) { out := new(github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.AppProject) err := grpc.Invoke(ctx, "/project.ProjectService/Create", in, out, c.cc, opts...) @@ -199,6 +319,10 @@ func (c *projectServiceClient) ListEvents(ctx context.Context, in *ProjectQuery, // Server API for ProjectService service type ProjectServiceServer interface { + // Create a new project token. + CreateToken(context.Context, *ProjectTokenCreateRequest) (*ProjectTokenResponse, error) + // Delete a new project token. + DeleteToken(context.Context, *ProjectTokenDeleteRequest) (*EmptyResponse, error) // Create a new project. Create(context.Context, *ProjectCreateRequest) (*github_com_argoproj_argo_cd_pkg_apis_application_v1alpha1.AppProject, error) // List returns list of projects @@ -217,6 +341,42 @@ func RegisterProjectServiceServer(s *grpc.Server, srv ProjectServiceServer) { s.RegisterService(&_ProjectService_serviceDesc, srv) } +func _ProjectService_CreateToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ProjectTokenCreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProjectServiceServer).CreateToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/project.ProjectService/CreateToken", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProjectServiceServer).CreateToken(ctx, req.(*ProjectTokenCreateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProjectService_DeleteToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ProjectTokenDeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProjectServiceServer).DeleteToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/project.ProjectService/DeleteToken", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProjectServiceServer).DeleteToken(ctx, req.(*ProjectTokenDeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _ProjectService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ProjectCreateRequest) if err := dec(in); err != nil { @@ -329,6 +489,14 @@ var _ProjectService_serviceDesc = grpc.ServiceDesc{ ServiceName: "project.ProjectService", HandlerType: (*ProjectServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "CreateToken", + Handler: _ProjectService_CreateToken_Handler, + }, + { + MethodName: "DeleteToken", + Handler: _ProjectService_DeleteToken_Handler, + }, { MethodName: "Create", Handler: _ProjectService_Create_Handler, @@ -386,6 +554,106 @@ func (m *ProjectCreateRequest) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *ProjectTokenDeleteRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProjectTokenDeleteRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Project) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintProject(dAtA, i, uint64(len(m.Project))) + i += copy(dAtA[i:], m.Project) + } + if len(m.Role) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintProject(dAtA, i, uint64(len(m.Role))) + i += copy(dAtA[i:], m.Role) + } + if m.Iat != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintProject(dAtA, i, uint64(m.Iat)) + } + return i, nil +} + +func (m *ProjectTokenCreateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProjectTokenCreateRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Project) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintProject(dAtA, i, uint64(len(m.Project))) + i += copy(dAtA[i:], m.Project) + } + if len(m.Description) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintProject(dAtA, i, uint64(len(m.Description))) + i += copy(dAtA[i:], m.Description) + } + if len(m.Role) > 0 { + dAtA[i] = 0x1a + i++ + i = encodeVarintProject(dAtA, i, uint64(len(m.Role))) + i += copy(dAtA[i:], m.Role) + } + if m.ExpiresIn != 0 { + dAtA[i] = 0x20 + i++ + i = encodeVarintProject(dAtA, i, uint64(m.ExpiresIn)) + } + return i, nil +} + +func (m *ProjectTokenResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProjectTokenResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Token) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintProject(dAtA, i, uint64(len(m.Token))) + i += copy(dAtA[i:], m.Token) + } + return i, nil +} + func (m *ProjectQuery) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -475,6 +743,54 @@ func (m *ProjectCreateRequest) Size() (n int) { return n } +func (m *ProjectTokenDeleteRequest) Size() (n int) { + var l int + _ = l + l = len(m.Project) + if l > 0 { + n += 1 + l + sovProject(uint64(l)) + } + l = len(m.Role) + if l > 0 { + n += 1 + l + sovProject(uint64(l)) + } + if m.Iat != 0 { + n += 1 + sovProject(uint64(m.Iat)) + } + return n +} + +func (m *ProjectTokenCreateRequest) Size() (n int) { + var l int + _ = l + l = len(m.Project) + if l > 0 { + n += 1 + l + sovProject(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovProject(uint64(l)) + } + l = len(m.Role) + if l > 0 { + n += 1 + l + sovProject(uint64(l)) + } + if m.ExpiresIn != 0 { + n += 1 + sovProject(uint64(m.ExpiresIn)) + } + return n +} + +func (m *ProjectTokenResponse) Size() (n int) { + var l int + _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovProject(uint64(l)) + } + return n +} + func (m *ProjectQuery) Size() (n int) { var l int _ = l @@ -597,6 +913,368 @@ func (m *ProjectCreateRequest) Unmarshal(dAtA []byte) error { } return nil } +func (m *ProjectTokenDeleteRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProjectTokenDeleteRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProjectTokenDeleteRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Project", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Project = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Role = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Iat", wireType) + } + m.Iat = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Iat |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipProject(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProject + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProjectTokenCreateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProjectTokenCreateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProjectTokenCreateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Project", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Project = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Role = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpiresIn", wireType) + } + m.ExpiresIn = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExpiresIn |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipProject(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProject + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProjectTokenResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProjectTokenResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProjectTokenResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProject + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProject + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProject(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProject + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ProjectQuery) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -917,38 +1595,49 @@ var ( func init() { proto.RegisterFile("server/project/project.proto", fileDescriptorProject) } var fileDescriptorProject = []byte{ - // 524 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x94, 0xcf, 0x6b, 0x13, 0x41, - 0x14, 0xc7, 0x19, 0x2d, 0x11, 0xc7, 0x9f, 0x0c, 0xad, 0xd6, 0xb5, 0x8d, 0x65, 0x0f, 0x52, 0x0a, - 0x9d, 0x21, 0xad, 0x87, 0xe2, 0xcd, 0x1f, 0x41, 0x0a, 0x1e, 0x34, 0x22, 0x88, 0x97, 0x32, 0xdd, - 0x7d, 0x6c, 0x36, 0xc9, 0xee, 0x8c, 0x33, 0x93, 0x95, 0x20, 0x5e, 0x8a, 0x37, 0x8f, 0x82, 0xff, - 0x80, 0xff, 0x8c, 0x47, 0xc1, 0x7f, 0x40, 0x82, 0x7f, 0x88, 0xcc, 0xdb, 0x5d, 0xd3, 0x34, 0x5d, - 0x4f, 0xc1, 0x53, 0x5e, 0xde, 0xcc, 0xbc, 0xef, 0xe7, 0xfd, 0xd8, 0x47, 0x37, 0x2c, 0x98, 0x02, - 0x8c, 0xd0, 0x46, 0x0d, 0x20, 0x72, 0xf5, 0x2f, 0xd7, 0x46, 0x39, 0xc5, 0x2e, 0x55, 0x7f, 0x83, - 0xd5, 0x44, 0x25, 0x0a, 0x7d, 0xc2, 0x5b, 0xe5, 0x71, 0xb0, 0x91, 0x28, 0x95, 0x8c, 0x40, 0x48, - 0x9d, 0x0a, 0x99, 0xe7, 0xca, 0x49, 0x97, 0xaa, 0xdc, 0x56, 0xa7, 0xe1, 0xf0, 0xc0, 0xf2, 0x54, - 0xe1, 0x69, 0xa4, 0x0c, 0x88, 0xa2, 0x23, 0x12, 0xc8, 0xc1, 0x48, 0x07, 0x71, 0x75, 0xe7, 0xc1, - 0xec, 0x4e, 0x26, 0xa3, 0x7e, 0x9a, 0x83, 0x99, 0x08, 0x3d, 0x4c, 0xbc, 0xc3, 0x8a, 0x0c, 0x9c, - 0x3c, 0xef, 0xd5, 0x61, 0x92, 0xba, 0xfe, 0xf8, 0x98, 0x47, 0x2a, 0x13, 0xd2, 0x20, 0xd8, 0x00, - 0x8d, 0xdd, 0x28, 0x9e, 0xbd, 0x96, 0x5a, 0x8f, 0xd2, 0x08, 0x91, 0x44, 0xd1, 0x91, 0x23, 0xdd, - 0x97, 0x0b, 0xa1, 0xc2, 0xf7, 0x74, 0xf5, 0x45, 0x99, 0xe3, 0x13, 0x03, 0xd2, 0x41, 0x0f, 0xde, - 0x8d, 0xc1, 0x3a, 0x76, 0x44, 0xeb, 0xdc, 0xd7, 0xc9, 0x16, 0xd9, 0xbe, 0xb2, 0xd7, 0xe5, 0x33, - 0x51, 0x5e, 0x8b, 0xa2, 0x71, 0x14, 0xc5, 0x5c, 0x0f, 0x13, 0xee, 0x45, 0xf9, 0x29, 0x51, 0x5e, - 0x8b, 0xf2, 0x47, 0x5a, 0x57, 0x22, 0xbd, 0x3a, 0x6a, 0x18, 0xd2, 0xab, 0x95, 0xef, 0xe5, 0x18, - 0xcc, 0x84, 0x31, 0xba, 0x92, 0xcb, 0x0c, 0x50, 0xed, 0x72, 0x0f, 0xed, 0x53, 0x70, 0xaf, 0x75, - 0xfc, 0x3f, 0xe1, 0x6e, 0xd0, 0x6b, 0xdd, 0x4c, 0xbb, 0x49, 0x0f, 0xac, 0x56, 0xb9, 0x85, 0xbd, - 0xaf, 0x2d, 0x7a, 0xbd, 0xba, 0xf5, 0x0a, 0x4c, 0x91, 0x46, 0xc0, 0x3e, 0x13, 0xda, 0x2a, 0x6b, - 0xc6, 0x36, 0x79, 0x3d, 0x36, 0xe7, 0xd5, 0x32, 0x58, 0x0e, 0x5d, 0x78, 0xf7, 0xe4, 0xe7, 0xef, - 0x2f, 0x17, 0xd6, 0xc2, 0x9b, 0x38, 0x51, 0x45, 0xa7, 0x9e, 0x55, 0xfb, 0x90, 0xec, 0xb0, 0x13, - 0x42, 0x57, 0x9e, 0xa7, 0xd6, 0xb1, 0xb5, 0xb3, 0x2c, 0x58, 0xde, 0xe0, 0x70, 0x29, 0x0c, 0x5e, - 0x21, 0x5c, 0x47, 0x0e, 0xc6, 0x16, 0x38, 0xd8, 0x27, 0x42, 0x2f, 0x3e, 0x83, 0x46, 0x86, 0x25, - 0xd5, 0xe1, 0x1e, 0xea, 0xdf, 0x61, 0xb7, 0xcf, 0xea, 0x8b, 0x0f, 0x7e, 0x6a, 0x3e, 0xb2, 0x6f, - 0x84, 0xb6, 0xca, 0x81, 0x59, 0xec, 0xcc, 0xdc, 0x20, 0x2d, 0x8b, 0x68, 0x1f, 0x89, 0x76, 0x83, - 0xed, 0x45, 0xa2, 0x5a, 0xde, 0x7f, 0xca, 0xb1, 0x74, 0x92, 0x23, 0xa2, 0xef, 0xd8, 0x1b, 0xda, - 0x7a, 0x0a, 0x23, 0x70, 0xd0, 0x54, 0xae, 0x5b, 0x7f, 0xdd, 0x73, 0xb3, 0x58, 0xe7, 0xbf, 0xd3, - 0x98, 0xff, 0x80, 0x52, 0xdf, 0xa8, 0x6e, 0x01, 0xb9, 0xb3, 0x4d, 0xd1, 0x37, 0x79, 0xb9, 0x7a, - 0x7c, 0x86, 0xdc, 0xaf, 0x27, 0x5e, 0x74, 0x38, 0x3e, 0xc1, 0x26, 0xdf, 0x47, 0x91, 0x2d, 0xd6, - 0x6e, 0x10, 0x11, 0x80, 0xd1, 0x1f, 0x1f, 0x7c, 0x9f, 0xb6, 0xc9, 0x8f, 0x69, 0x9b, 0xfc, 0x9a, - 0xb6, 0xc9, 0xdb, 0x9d, 0x7f, 0x2d, 0xa6, 0xf9, 0x4d, 0x7b, 0xdc, 0xc2, 0x05, 0xb4, 0xff, 0x27, - 0x00, 0x00, 0xff, 0xff, 0x07, 0x9f, 0x39, 0xbd, 0x82, 0x05, 0x00, 0x00, + // 689 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0x5d, 0x6b, 0x13, 0x4d, + 0x14, 0x66, 0x9a, 0xbe, 0x79, 0xed, 0xc4, 0x8f, 0x32, 0xb4, 0x9a, 0xc6, 0x36, 0x86, 0xb9, 0x90, + 0x12, 0xec, 0x0c, 0x69, 0x15, 0x8a, 0x77, 0x7e, 0x14, 0x29, 0x78, 0xa1, 0x51, 0x41, 0xf4, 0xa2, + 0x4c, 0x37, 0x87, 0xed, 0x36, 0xc9, 0xce, 0x38, 0x3b, 0x5d, 0x2d, 0xa5, 0x20, 0xc5, 0x1b, 0xf5, + 0xd2, 0x9f, 0x20, 0xf8, 0x5b, 0xbc, 0x14, 0xfc, 0x03, 0x12, 0xfc, 0x21, 0x32, 0xb3, 0xbb, 0x49, + 0xb6, 0xe9, 0x16, 0x84, 0xe0, 0x55, 0xce, 0x9e, 0x39, 0x73, 0x9e, 0xe7, 0x39, 0x1f, 0x19, 0xbc, + 0x1c, 0x81, 0x8e, 0x41, 0x73, 0xa5, 0xe5, 0x3e, 0x78, 0x26, 0xfb, 0x65, 0x4a, 0x4b, 0x23, 0xc9, + 0xff, 0xe9, 0x67, 0x6d, 0xc1, 0x97, 0xbe, 0x74, 0x3e, 0x6e, 0xad, 0xe4, 0xb8, 0xb6, 0xec, 0x4b, + 0xe9, 0xf7, 0x80, 0x0b, 0x15, 0x70, 0x11, 0x86, 0xd2, 0x08, 0x13, 0xc8, 0x30, 0x4a, 0x4f, 0x69, + 0x77, 0x33, 0x62, 0x81, 0x74, 0xa7, 0x9e, 0xd4, 0xc0, 0xe3, 0x16, 0xf7, 0x21, 0x04, 0x2d, 0x0c, + 0x74, 0xd2, 0x98, 0xdb, 0xa3, 0x98, 0xbe, 0xf0, 0xf6, 0x82, 0x10, 0xf4, 0x21, 0x57, 0x5d, 0xdf, + 0x3a, 0x22, 0xde, 0x07, 0x23, 0xce, 0xba, 0xb5, 0xed, 0x07, 0x66, 0xef, 0x60, 0x97, 0x79, 0xb2, + 0xcf, 0x85, 0x76, 0xc4, 0xf6, 0x9d, 0xb1, 0xe6, 0x75, 0x46, 0xb7, 0x85, 0x52, 0xbd, 0xc0, 0x73, + 0x94, 0x78, 0xdc, 0x12, 0x3d, 0xb5, 0x27, 0x26, 0x52, 0xd1, 0xb7, 0x78, 0xe1, 0x49, 0xa2, 0xf1, + 0x81, 0x06, 0x61, 0xa0, 0x0d, 0x6f, 0x0e, 0x20, 0x32, 0x64, 0x07, 0x67, 0xda, 0xab, 0xa8, 0x81, + 0x56, 0x2b, 0xeb, 0x5b, 0x6c, 0x04, 0xca, 0x32, 0x50, 0x67, 0xec, 0x78, 0x1d, 0xa6, 0xba, 0x3e, + 0xb3, 0xa0, 0x6c, 0x0c, 0x94, 0x65, 0xa0, 0xec, 0x9e, 0x52, 0x29, 0x48, 0x3b, 0xcb, 0x4a, 0x5f, + 0xe3, 0xa5, 0xd4, 0xf7, 0x5c, 0x76, 0x21, 0x7c, 0x08, 0x3d, 0x18, 0xa1, 0x57, 0xf3, 0xe8, 0x73, + 0xc3, 0x6b, 0x84, 0xe0, 0x59, 0x2d, 0x7b, 0x50, 0x9d, 0x71, 0x6e, 0x67, 0x93, 0x79, 0x5c, 0x0a, + 0x84, 0xa9, 0x96, 0x1a, 0x68, 0xb5, 0xd4, 0xb6, 0x26, 0xfd, 0x88, 0xf2, 0xd9, 0xf3, 0xda, 0x8a, + 0xb3, 0x37, 0x70, 0xa5, 0x03, 0x91, 0xa7, 0x03, 0x65, 0x05, 0xa4, 0x20, 0xe3, 0xae, 0x21, 0x7e, + 0x69, 0x0c, 0x7f, 0x19, 0xcf, 0xc1, 0x3b, 0x15, 0x68, 0x88, 0xb6, 0xc3, 0xea, 0xac, 0x63, 0x31, + 0x72, 0xd0, 0x5b, 0xc3, 0x0a, 0x3b, 0x2a, 0x6d, 0x88, 0x94, 0x0c, 0x23, 0x20, 0x0b, 0xf8, 0x3f, + 0x63, 0x1d, 0x29, 0x87, 0xe4, 0x83, 0x52, 0x7c, 0x31, 0x8d, 0x7e, 0x7a, 0x00, 0xfa, 0xd0, 0xe2, + 0x85, 0xa2, 0x0f, 0x69, 0x90, 0xb3, 0xc7, 0x7a, 0xf6, 0x42, 0x75, 0xfe, 0x65, 0xcf, 0xae, 0xe0, + 0x4b, 0x5b, 0x7d, 0x65, 0x0e, 0x33, 0x0d, 0xeb, 0xdf, 0x2e, 0xe0, 0xcb, 0x69, 0xd4, 0x33, 0xd0, + 0x71, 0xe0, 0x01, 0xf9, 0x84, 0x70, 0x25, 0x29, 0xb7, 0x93, 0x4b, 0x28, 0xcb, 0x56, 0xaa, 0xb0, + 0x21, 0xb5, 0x95, 0x33, 0x63, 0x32, 0x14, 0xba, 0x79, 0xf2, 0xf3, 0xf7, 0x97, 0x99, 0x75, 0xba, + 0xe6, 0x56, 0x29, 0x6e, 0x65, 0x4b, 0x1a, 0xf1, 0xa3, 0xd4, 0x3a, 0xe6, 0xb6, 0x11, 0x11, 0x3f, + 0xb2, 0x3f, 0xc7, 0xdc, 0x95, 0xf2, 0x2e, 0x6a, 0x92, 0xf7, 0x08, 0x57, 0x92, 0xc9, 0x3a, 0x8f, + 0x4c, 0x6e, 0xf6, 0x6a, 0x57, 0x87, 0x31, 0x39, 0xad, 0xf4, 0x8e, 0x63, 0xc1, 0x9b, 0x7f, 0xc7, + 0x82, 0x7c, 0x46, 0xb8, 0x9c, 0xa8, 0x25, 0x13, 0x32, 0xf3, 0x55, 0x98, 0x4e, 0xb7, 0xe8, 0x75, + 0xc7, 0x73, 0x91, 0xce, 0x9f, 0xe6, 0x69, 0x0b, 0x72, 0x82, 0xf0, 0xec, 0xe3, 0x20, 0x32, 0x64, + 0xf1, 0x34, 0x17, 0x37, 0x6e, 0xb5, 0xed, 0xa9, 0x70, 0xb0, 0x08, 0xb4, 0xea, 0x78, 0x10, 0x32, + 0xc1, 0x83, 0x7c, 0x40, 0xb8, 0xf4, 0x08, 0x0a, 0x39, 0x4c, 0xa9, 0x0e, 0x37, 0x1c, 0xfe, 0x12, + 0xb9, 0x36, 0xd9, 0x2f, 0xbb, 0x45, 0xc7, 0xe4, 0x2b, 0xc2, 0xe5, 0x64, 0x81, 0x26, 0x3b, 0x93, + 0x5b, 0xac, 0x69, 0x31, 0xda, 0x70, 0x8c, 0xd6, 0x6a, 0xab, 0x85, 0x13, 0xc4, 0xec, 0x3f, 0x7e, + 0x47, 0x18, 0xc1, 0x1c, 0x45, 0xdb, 0xb1, 0x97, 0xb8, 0x9c, 0xcc, 0x67, 0x51, 0xb9, 0x8a, 0xe6, + 0x35, 0xd5, 0xdf, 0x2c, 0xd4, 0xbf, 0x8f, 0xb1, 0x6d, 0xd4, 0x56, 0x0c, 0xa1, 0x89, 0x8a, 0xb2, + 0xaf, 0xb0, 0xe4, 0x85, 0xb2, 0x0a, 0x99, 0x7d, 0xc5, 0x58, 0xdc, 0x62, 0xee, 0x8a, 0x6b, 0xf2, + 0x4d, 0x07, 0xd2, 0x20, 0xf5, 0x02, 0x10, 0x0e, 0x2e, 0xfb, 0xfd, 0xcd, 0xef, 0x83, 0x3a, 0xfa, + 0x31, 0xa8, 0xa3, 0x5f, 0x83, 0x3a, 0x7a, 0xd5, 0x3c, 0xef, 0xfd, 0xca, 0x3f, 0xc8, 0xbb, 0x65, + 0xf7, 0x4e, 0x6d, 0xfc, 0x09, 0x00, 0x00, 0xff, 0xff, 0x53, 0xd4, 0xec, 0x49, 0xa9, 0x07, 0x00, + 0x00, } diff --git a/server/project/project.pb.gw.go b/server/project/project.pb.gw.go index a1b8f016ef993..6b128b882233a 100644 --- a/server/project/project.pb.gw.go +++ b/server/project/project.pb.gw.go @@ -28,6 +28,94 @@ var _ status.Status var _ = runtime.String var _ = utilities.NewDoubleArray +func request_ProjectService_CreateToken_0(ctx context.Context, marshaler runtime.Marshaler, client ProjectServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ProjectTokenCreateRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["project"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "project") + } + + protoReq.Project, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "project", err) + } + + val, ok = pathParams["role"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "role") + } + + protoReq.Role, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "role", err) + } + + msg, err := client.CreateToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +var ( + filter_ProjectService_DeleteToken_0 = &utilities.DoubleArray{Encoding: map[string]int{"project": 0, "role": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}} +) + +func request_ProjectService_DeleteToken_0(ctx context.Context, marshaler runtime.Marshaler, client ProjectServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ProjectTokenDeleteRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["project"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "project") + } + + protoReq.Project, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "project", err) + } + + val, ok = pathParams["role"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "role") + } + + protoReq.Role, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "role", err) + } + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ProjectService_DeleteToken_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DeleteToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_ProjectService_Create_0(ctx context.Context, marshaler runtime.Marshaler, client ProjectServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ProjectCreateRequest var metadata runtime.ServerMetadata @@ -208,6 +296,64 @@ func RegisterProjectServiceHandler(ctx context.Context, mux *runtime.ServeMux, c // "ProjectServiceClient" to call the correct interceptors. func RegisterProjectServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ProjectServiceClient) error { + mux.Handle("POST", pattern_ProjectService_CreateToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ProjectService_CreateToken_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_ProjectService_CreateToken_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_ProjectService_DeleteToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ProjectService_DeleteToken_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_ProjectService_DeleteToken_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_ProjectService_Create_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -386,6 +532,10 @@ func RegisterProjectServiceHandlerClient(ctx context.Context, mux *runtime.Serve } var ( + pattern_ProjectService_CreateToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"api", "v1", "projects", "project", "roles", "role", "token"}, "")) + + pattern_ProjectService_DeleteToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"api", "v1", "projects", "project", "roles", "role", "token"}, "")) + pattern_ProjectService_Create_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "projects"}, "")) pattern_ProjectService_List_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "projects"}, "")) @@ -400,6 +550,10 @@ var ( ) var ( + forward_ProjectService_CreateToken_0 = runtime.ForwardResponseMessage + + forward_ProjectService_DeleteToken_0 = runtime.ForwardResponseMessage + forward_ProjectService_Create_0 = runtime.ForwardResponseMessage forward_ProjectService_List_0 = runtime.ForwardResponseMessage diff --git a/server/project/project.proto b/server/project/project.proto index af8ba0eb93918..5cd75d61485fc 100644 --- a/server/project/project.proto +++ b/server/project/project.proto @@ -18,6 +18,27 @@ message ProjectCreateRequest { github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.AppProject project = 1; } +// ProjectTokenCreateRequest defines project token deletion parameters. +message ProjectTokenDeleteRequest { + string project = 1; + string role = 2; + int64 iat = 3; +} + +// ProjectTokenCreateRequest defines project token creation parameters. +message ProjectTokenCreateRequest { + string project = 1; + string description = 2; + string role = 3; + // expiresIn represents a duration in seconds + int64 expiresIn = 4; +} +// ProjectTokenResponse wraps the created token or returns an empty string if deleted. +message ProjectTokenResponse { + string token = 1; +} + + // ProjectQuery is a query for Project resources message ProjectQuery { string name = 1; @@ -32,6 +53,19 @@ message EmptyResponse {} // ProjectService service ProjectService { + // Create a new project token. + rpc CreateToken(ProjectTokenCreateRequest) returns (ProjectTokenResponse) { + option (google.api.http) = { + post: "/api/v1/projects/{project}/roles/{role}/token" + body: "*" + }; + } + + // Delete a new project token. + rpc DeleteToken(ProjectTokenDeleteRequest) returns (EmptyResponse) { + option (google.api.http).delete = "/api/v1/projects/{project}/roles/{role}/token"; + } + // Create a new project. rpc Create(ProjectCreateRequest) returns (github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.AppProject) { option (google.api.http) = { diff --git a/server/project/project_test.go b/server/project/project_test.go index c62d967686934..981b8ce71a9a7 100644 --- a/server/project/project_test.go +++ b/server/project/project_test.go @@ -2,6 +2,7 @@ package project import ( "context" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -15,13 +16,19 @@ import ( apps "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake" "github.com/argoproj/argo-cd/test" "github.com/argoproj/argo-cd/util" + jwtutil "github.com/argoproj/argo-cd/util/jwt" "github.com/argoproj/argo-cd/util/rbac" + "github.com/argoproj/argo-cd/util/session" + "github.com/argoproj/argo-cd/util/settings" ) func TestProjectServer(t *testing.T) { enforcer := rbac.NewEnforcer(fake.NewSimpleClientset(), "default", common.ArgoCDRBACConfigMapName, nil) enforcer.SetBuiltinPolicy(test.BuiltinPolicy) enforcer.SetDefaultRole("role:admin") + enforcer.SetClaimsEnforcerFunc(func(rvals ...interface{}) bool { + return true + }) existingProj := v1alpha1.AppProject{ ObjectMeta: v1.ObjectMeta{Name: "test", Namespace: "default"}, Spec: v1alpha1.AppProjectSpec{ @@ -33,13 +40,15 @@ func TestProjectServer(t *testing.T) { }, } + policyTemplate := "p, proj:%s:%s, applications, %s, %s/%s, %s" + t.Run("TestRemoveDestinationSuccessful", func(t *testing.T) { existingApp := v1alpha1.Application{ ObjectMeta: v1.ObjectMeta{Name: "test", Namespace: "default"}, Spec: v1alpha1.ApplicationSpec{Project: "test", Destination: v1alpha1.ApplicationDestination{Namespace: "ns3", Server: "https://server3"}}, } - projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock()) + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock(), nil) updatedProj := existingProj.DeepCopy() updatedProj.Spec.Destinations = updatedProj.Spec.Destinations[1:] @@ -55,7 +64,7 @@ func TestProjectServer(t *testing.T) { Spec: v1alpha1.ApplicationSpec{Project: "test", Destination: v1alpha1.ApplicationDestination{Namespace: "ns1", Server: "https://server1"}}, } - projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock()) + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock(), nil) updatedProj := existingProj.DeepCopy() updatedProj.Spec.Destinations = updatedProj.Spec.Destinations[1:] @@ -72,7 +81,7 @@ func TestProjectServer(t *testing.T) { Spec: v1alpha1.ApplicationSpec{Project: "test"}, } - projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock()) + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock(), nil) updatedProj := existingProj.DeepCopy() updatedProj.Spec.SourceRepos = []string{} @@ -88,7 +97,7 @@ func TestProjectServer(t *testing.T) { Spec: v1alpha1.ApplicationSpec{Project: "test", Source: v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argo-cd.git"}}, } - projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock()) + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock(), nil) updatedProj := existingProj.DeepCopy() updatedProj.Spec.SourceRepos = []string{} @@ -100,7 +109,7 @@ func TestProjectServer(t *testing.T) { }) t.Run("TestDeleteProjectSuccessful", func(t *testing.T) { - projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj), enforcer, util.NewKeyLock()) + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj), enforcer, util.NewKeyLock(), nil) _, err := projectServer.Delete(context.Background(), &ProjectQuery{Name: "test"}) @@ -113,11 +122,184 @@ func TestProjectServer(t *testing.T) { Spec: v1alpha1.ApplicationSpec{Project: "test"}, } - projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock()) + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, util.NewKeyLock(), nil) _, err := projectServer.Delete(context.Background(), &ProjectQuery{Name: "test"}) assert.NotNil(t, err) assert.Equal(t, codes.InvalidArgument, grpc.Code(err)) }) + + t.Run("TestCreateTokenSuccesfully", func(t *testing.T) { + sessionMgr := session.NewSessionManager(&settings.ArgoCDSettings{}) + projectWithRole := existingProj.DeepCopy() + tokenName := "testToken" + projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}} + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, util.NewKeyLock(), sessionMgr) + tokenResponse, err := projectServer.CreateToken(context.Background(), &ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1}) + assert.Nil(t, err) + claims, err := sessionMgr.Parse(tokenResponse.Token) + assert.Nil(t, err) + + mapClaims, err := jwtutil.MapClaims(claims) + subject, ok := mapClaims["sub"].(string) + assert.True(t, ok) + expectedSubject := fmt.Sprintf(JWTTokenSubFormat, projectWithRole.Name, tokenName) + assert.Equal(t, expectedSubject, subject) + assert.Nil(t, err) + }) + + t.Run("TestDeleteTokenSuccesfully", func(t *testing.T) { + sessionMgr := session.NewSessionManager(&settings.ArgoCDSettings{}) + projWithToken := existingProj.DeepCopy() + tokenName := "testToken" + issuedAt := int64(1) + secondIssuedAt := issuedAt + 1 + token := v1alpha1.ProjectRole{Name: tokenName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: issuedAt}, {IssuedAt: secondIssuedAt}}} + projWithToken.Spec.Roles = append(projWithToken.Spec.Roles, token) + + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithToken), enforcer, util.NewKeyLock(), sessionMgr) + _, err := projectServer.DeleteToken(context.Background(), &ProjectTokenDeleteRequest{Project: projWithToken.Name, Role: tokenName, Iat: issuedAt}) + assert.Nil(t, err) + projWithoutToken, err := projectServer.Get(context.Background(), &ProjectQuery{Name: projWithToken.Name}) + assert.Nil(t, err) + assert.Len(t, projWithoutToken.Spec.Roles, 1) + assert.Len(t, projWithoutToken.Spec.Roles[0].JWTTokens, 1) + assert.Equal(t, projWithoutToken.Spec.Roles[0].JWTTokens[0].IssuedAt, secondIssuedAt) + }) + + t.Run("TestCreateTwoTokensInRoleSuccess", func(t *testing.T) { + sessionMgr := session.NewSessionManager(&settings.ArgoCDSettings{}) + projWithToken := existingProj.DeepCopy() + tokenName := "testToken" + token := v1alpha1.ProjectRole{Name: tokenName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}} + projWithToken.Spec.Roles = append(projWithToken.Spec.Roles, token) + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithToken), enforcer, util.NewKeyLock(), sessionMgr) + _, err := projectServer.CreateToken(context.Background(), &ProjectTokenCreateRequest{Project: projWithToken.Name, Role: tokenName}) + assert.Nil(t, err) + projWithTwoTokens, err := projectServer.Get(context.Background(), &ProjectQuery{Name: projWithToken.Name}) + assert.Nil(t, err) + assert.Len(t, projWithTwoTokens.Spec.Roles, 1) + assert.Len(t, projWithTwoTokens.Spec.Roles[0].JWTTokens, 2) + }) + + t.Run("TestCreateRolePolicySuccessfully", func(t *testing.T) { + action := "create" + object := "testApplication" + roleName := "testRole" + effect := "allow" + + projWithRole := existingProj.DeepCopy() + role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}} + policy := fmt.Sprintf(policyTemplate, projWithRole.Name, roleName, action, projWithRole.Name, object, effect) + role.Policies = append(role.Policies, policy) + projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role) + + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, util.NewKeyLock(), nil) + request := &ProjectUpdateRequest{Project: projWithRole} + _, err := projectServer.Update(context.Background(), request) + assert.Nil(t, err) + t.Log(projWithRole.Spec.Roles[0].Policies[0]) + expectedPolicy := fmt.Sprintf(policyTemplate, projWithRole.Name, role.Name, action, projWithRole.Name, object, effect) + assert.Equal(t, projWithRole.Spec.Roles[0].Policies[0], expectedPolicy) + }) + + t.Run("TestValidatePolicyDuplicatePolicyFailure", func(t *testing.T) { + action := "create" + object := "testApplication" + roleName := "testRole" + effect := "allow" + + projWithRole := existingProj.DeepCopy() + role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}} + policy := fmt.Sprintf(policyTemplate, projWithRole.Name, roleName, action, projWithRole.Name, object, effect) + role.Policies = append(role.Policies, policy) + role.Policies = append(role.Policies, policy) + projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role) + + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, util.NewKeyLock(), nil) + request := &ProjectUpdateRequest{Project: projWithRole} + _, err := projectServer.Update(context.Background(), request) + expectedErr := fmt.Sprintf("rpc error: code = AlreadyExists desc = policy '%s' already exists for role '%s'", policy, roleName) + assert.EqualError(t, err, expectedErr) + }) + + t.Run("TestValidateProjectAccessToSeparateProjectObjectFailure", func(t *testing.T) { + action := "create" + object := "testApplication" + roleName := "testRole" + otherProject := "other-project" + effect := "allow" + + projWithRole := existingProj.DeepCopy() + role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}} + policy := fmt.Sprintf(policyTemplate, projWithRole.Name, roleName, action, otherProject, object, effect) + role.Policies = append(role.Policies, policy) + projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role) + + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, util.NewKeyLock(), nil) + request := &ProjectUpdateRequest{Project: projWithRole} + _, err := projectServer.Update(context.Background(), request) + expectedErr := fmt.Sprintf("rpc error: code = InvalidArgument desc = incorrect policy format for '%s' as policies can't grant access to other projects", policy) + assert.EqualError(t, err, expectedErr) + }) + + t.Run("TestValidateProjectIncorrectProjectInRoleFailure", func(t *testing.T) { + action := "create" + object := "testApplication" + roleName := "testRole" + otherProject := "other-project" + effect := "allow" + + projWithRole := existingProj.DeepCopy() + role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}} + invalidPolicy := fmt.Sprintf(policyTemplate, otherProject, roleName, action, projWithRole.Name, object, effect) + role.Policies = append(role.Policies, invalidPolicy) + projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role) + + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, util.NewKeyLock(), nil) + request := &ProjectUpdateRequest{Project: projWithRole} + _, err := projectServer.Update(context.Background(), request) + expectedErr := fmt.Sprintf("rpc error: code = InvalidArgument desc = incorrect policy format for '%s' as policy can't grant access to other projects", invalidPolicy) + assert.EqualError(t, err, expectedErr) + }) + + t.Run("TestValidateProjectIncorrectTokenInRoleFailure", func(t *testing.T) { + action := "create" + object := "testApplication" + roleName := "testRole" + otherToken := "other-token" + effect := "allow" + + projWithRole := existingProj.DeepCopy() + role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}} + invalidPolicy := fmt.Sprintf(policyTemplate, projWithRole.Name, otherToken, action, projWithRole.Name, object, effect) + role.Policies = append(role.Policies, invalidPolicy) + projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role) + + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, util.NewKeyLock(), nil) + request := &ProjectUpdateRequest{Project: projWithRole} + _, err := projectServer.Update(context.Background(), request) + expectedErr := fmt.Sprintf("rpc error: code = InvalidArgument desc = incorrect policy format for '%s' as policy can't grant access to other roles", invalidPolicy) + assert.EqualError(t, err, expectedErr) + }) + + t.Run("TestValidateProjectInvalidEffectFailure", func(t *testing.T) { + action := "create" + object := "testApplication" + roleName := "testRole" + effect := "testEffect" + + projWithRole := existingProj.DeepCopy() + role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}} + invalidPolicy := fmt.Sprintf(policyTemplate, projWithRole.Name, roleName, action, projWithRole.Name, object, effect) + role.Policies = append(role.Policies, invalidPolicy) + projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role) + + projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, util.NewKeyLock(), nil) + request := &ProjectUpdateRequest{Project: projWithRole} + _, err := projectServer.Update(context.Background(), request) + expectedErr := fmt.Sprintf("rpc error: code = InvalidArgument desc = incorrect policy format for '%s' as effect can only have value 'allow' or 'deny'", invalidPolicy) + assert.EqualError(t, err, expectedErr) + }) } diff --git a/server/server.go b/server/server.go index 7b0a9ac59f8e3..aacbc7ea04da4 100644 --- a/server/server.go +++ b/server/server.go @@ -12,6 +12,7 @@ import ( "strings" "time" + jwt "github.com/dgrijalva/jwt-go" "github.com/gobuffalo/packr" golang_proto "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/go-grpc-middleware" @@ -20,12 +21,14 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/runtime" log "github.com/sirupsen/logrus" "github.com/soheilhy/cmux" + netCtx "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" @@ -49,13 +52,14 @@ import ( dexutil "github.com/argoproj/argo-cd/util/dex" grpc_util "github.com/argoproj/argo-cd/util/grpc" jsonutil "github.com/argoproj/argo-cd/util/json" + jwtutil "github.com/argoproj/argo-cd/util/jwt" + projectutil "github.com/argoproj/argo-cd/util/project" "github.com/argoproj/argo-cd/util/rbac" util_session "github.com/argoproj/argo-cd/util/session" settings_util "github.com/argoproj/argo-cd/util/settings" "github.com/argoproj/argo-cd/util/swagger" tlsutil "github.com/argoproj/argo-cd/util/tls" "github.com/argoproj/argo-cd/util/webhook" - netCtx "golang.org/x/net/context" ) var ( @@ -332,7 +336,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server { grpc_util.ErrorCodeUnaryServerInterceptor(), grpc_util.PanicLoggerUnaryServerInterceptor(a.log), ))) - + a.enf.SetClaimsEnforcerFunc(EnforceClaims(a.enf, a.AppClientset, a.Namespace)) grpcS := grpc.NewServer(sOpts...) db := db.NewDB(a.Namespace, a.KubeClientset) clusterService := cluster.NewServer(db, a.enf) @@ -340,7 +344,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server { sessionService := session.NewServer(a.sessionMgr) projectLock := util.NewKeyLock() applicationService := application.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.RepoClientset, db, a.enf, projectLock) - projectService := project.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.enf, projectLock) + projectService := project.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.enf, projectLock, a.sessionMgr) settingsService := settings.NewServer(a.settingsMgr) accountService := account.NewServer(a.sessionMgr, a.settingsMgr) version.RegisterVersionServiceServer(grpcS, &version.Server{}) @@ -593,3 +597,71 @@ func bug21955WorkaroundInterceptor(ctx context.Context, req interface{}, _ *grpc } return handler(ctx, req) } + +func EnforceClaims(enf *rbac.Enforcer, a appclientset.Interface, namespace string) func(rvals ...interface{}) bool { + return func(rvals ...interface{}) bool { + claims, ok := rvals[0].(jwt.Claims) + if !ok { + if rvals[0] == nil { + vals := append([]interface{}{""}, rvals[1:]...) + return enf.Enforce(vals...) + } + return enf.Enforce(rvals...) + } + + mapClaims, err := jwtutil.MapClaims(claims) + if err != nil { + vals := append([]interface{}{""}, rvals[1:]...) + return enf.Enforce(vals...) + } + groups := jwtutil.GetGroups(mapClaims) + for _, group := range groups { + vals := append([]interface{}{group}, rvals[1:]...) + if enf.Enforcer.Enforce(vals...) { + return true + } + } + + user := jwtutil.GetField(mapClaims, "sub") + if strings.HasPrefix(user, "proj:") { + return enforceProjectToken(enf, a, namespace, user, mapClaims, rvals...) + } + vals := append([]interface{}{user}, rvals[1:]...) + return enf.Enforce(vals...) + } +} + +func enforceProjectToken(enf *rbac.Enforcer, a appclientset.Interface, namespace string, user string, claims jwt.MapClaims, rvals ...interface{}) bool { + userSplit := strings.Split(user, ":") + if len(userSplit) != 3 { + return false + } + projName := userSplit[1] + tokenName := userSplit[2] + proj, err := a.ArgoprojV1alpha1().AppProjects(namespace).Get(projName, metav1.GetOptions{}) + if err != nil { + return false + } + index, err := projectutil.GetRoleIndexByName(proj, tokenName) + if err != nil { + return false + } + if proj.Spec.Roles[index].JWTTokens == nil { + return false + } + iatField, ok := claims["iat"] + if !ok { + return false + } + iatFloat, ok := iatField.(float64) + if !ok { + return false + } + iat := int64(iatFloat) + _, err = projectutil.GetJWTTokenIndexByIssuedAt(proj, index, iat) + if err != nil { + return false + } + vals := append([]interface{}{user}, rvals[1:]...) + return enf.EnforceCustomPolicy(proj.ProjectPoliciesString(), vals...) +} diff --git a/server/server_test.go b/server/server_test.go new file mode 100644 index 0000000000000..46a564a1446a4 --- /dev/null +++ b/server/server_test.go @@ -0,0 +1,193 @@ +package server + +import ( + "fmt" + "testing" + + jwt "github.com/dgrijalva/jwt-go" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + apiv1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + + "github.com/argoproj/argo-cd/common" + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + apps "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake" + "github.com/argoproj/argo-cd/util/rbac" +) + +const ( + fakeNamespace = "fake-ns" + builtinPolicyFile = "builtin-policy.csv" +) + +func fakeConfigMap() *apiv1.ConfigMap { + cm := apiv1.ConfigMap{ + TypeMeta: v1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: v1.ObjectMeta{ + Name: common.ArgoCDConfigMapName, + Namespace: fakeNamespace, + }, + Data: make(map[string]string), + } + return &cm +} + +func fakeSecret(policy ...string) *apiv1.Secret { + secret := apiv1.Secret{ + TypeMeta: v1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: v1.ObjectMeta{ + Name: common.ArgoCDSecretName, + Namespace: fakeNamespace, + }, + Data: make(map[string][]byte), + } + return &secret +} + +func TestEnforceProjectToken(t *testing.T) { + projectName := "testProj" + roleName := "testRole" + subFormat := "proj:%s:%s" + policyTemplate := "p, %s, applications, get, %s/%s, %s" + + defaultObject := "*" + defaultEffect := "allow" + defaultTestObject := fmt.Sprintf("%s/%s", projectName, "test") + defaultIssuedAt := int64(1) + defaultSub := fmt.Sprintf(subFormat, projectName, roleName) + defaultPolicy := fmt.Sprintf(policyTemplate, defaultSub, projectName, defaultObject, defaultEffect) + + role := v1alpha1.ProjectRole{Name: roleName, Policies: []string{defaultPolicy}, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: defaultIssuedAt}}} + existingProj := v1alpha1.AppProject{ + ObjectMeta: v1.ObjectMeta{Name: projectName, Namespace: fakeNamespace}, + Spec: v1alpha1.AppProjectSpec{ + Roles: []v1alpha1.ProjectRole{role}, + }, + } + cm := fakeConfigMap() + secret := fakeSecret() + kubeclientset := fake.NewSimpleClientset(cm, secret) + + t.Run("TestEnforceProjectTokenSuccessful", func(t *testing.T) { + s := NewServer(ArgoCDServerOpts{Namespace: fakeNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)}) + s.newGRPCServer() + claims := jwt.MapClaims{"sub": defaultSub, "iat": defaultIssuedAt} + assert.True(t, s.enf.EnforceClaims(claims, "applications", "get", defaultTestObject)) + }) + + t.Run("TestEnforceProjectTokenWithDiffCreateAtFailure", func(t *testing.T) { + s := NewServer(ArgoCDServerOpts{Namespace: fakeNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)}) + s.newGRPCServer() + diffCreateAt := defaultIssuedAt + 1 + claims := jwt.MapClaims{"sub": defaultSub, "iat": diffCreateAt} + assert.False(t, s.enf.EnforceClaims(claims, "applications", "get", defaultTestObject)) + }) + + t.Run("TestEnforceProjectTokenIncorrectSubFormatFailure", func(t *testing.T) { + s := NewServer(ArgoCDServerOpts{Namespace: fakeNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)}) + s.newGRPCServer() + invalidSub := "proj:test" + claims := jwt.MapClaims{"sub": invalidSub, "iat": defaultIssuedAt} + assert.False(t, s.enf.EnforceClaims(claims, "applications", "get", defaultTestObject)) + }) + + t.Run("TestEnforceProjectTokenNoTokenFailure", func(t *testing.T) { + s := NewServer(ArgoCDServerOpts{Namespace: fakeNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)}) + s.newGRPCServer() + nonExistentToken := "fake-token" + invalidSub := fmt.Sprintf(subFormat, projectName, nonExistentToken) + claims := jwt.MapClaims{"sub": invalidSub, "iat": defaultIssuedAt} + + assert.False(t, s.enf.EnforceClaims(claims, "applications", "get", defaultTestObject)) + }) + + t.Run("TestEnforceProjectTokenNotJWTTokenFailure", func(t *testing.T) { + proj := existingProj.DeepCopy() + proj.Spec.Roles[0].JWTTokens = nil + s := NewServer(ArgoCDServerOpts{Namespace: fakeNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(proj)}) + s.newGRPCServer() + claims := jwt.MapClaims{"sub": defaultSub, "iat": defaultIssuedAt} + assert.False(t, s.enf.EnforceClaims(claims, "applications", "get", defaultTestObject)) + }) + + t.Run("TestEnforceProjectTokenExplicitDeny", func(t *testing.T) { + denyApp := "testDenyApp" + allowPolicy := fmt.Sprintf(policyTemplate, defaultSub, projectName, defaultObject, defaultEffect) + denyPolicy := fmt.Sprintf(policyTemplate, defaultSub, projectName, denyApp, "deny") + role := v1alpha1.ProjectRole{Name: roleName, Policies: []string{allowPolicy, denyPolicy}, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: defaultIssuedAt}}} + proj := existingProj.DeepCopy() + proj.Spec.Roles[0] = role + + s := NewServer(ArgoCDServerOpts{Namespace: fakeNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(proj)}) + s.newGRPCServer() + + claims := jwt.MapClaims{"sub": defaultSub, "iat": defaultIssuedAt} + allowedObject := fmt.Sprintf("%s/%s", projectName, "test") + denyObject := fmt.Sprintf("%s/%s", projectName, denyApp) + assert.True(t, s.enf.EnforceClaims(claims, "applications", "get", allowedObject)) + assert.False(t, s.enf.EnforceClaims(claims, "applications", "get", denyObject)) + }) +} + +func TestEnforceClaims(t *testing.T) { + kubeclientset := fake.NewSimpleClientset(fakeConfigMap()) + + enf := rbac.NewEnforcer(kubeclientset, fakeNamespace, common.ArgoCDConfigMapName, nil) + enf.SetBuiltinPolicy(box.String(builtinPolicyFile)) + enf.SetClaimsEnforcerFunc(EnforceClaims(enf, nil, fakeNamespace)) + policy := ` +g, org2:team2, role:admin +g, bob, role:admin +` + enf.SetUserPolicy(policy) + allowed := []jwt.Claims{ + jwt.MapClaims{"groups": []string{"org1:team1", "org2:team2"}}, + jwt.StandardClaims{Subject: "admin"}, + } + for _, c := range allowed { + if !assert.True(t, enf.EnforceClaims(c, "applications", "delete", "foo/obj")) { + log.Errorf("%v: expected true, got false", c) + } + } + + disallowed := []jwt.Claims{ + jwt.MapClaims{"groups": []string{"org3:team3"}}, + jwt.StandardClaims{Subject: "nobody"}, + } + for _, c := range disallowed { + if !assert.False(t, enf.EnforceClaims(c, "applications", "delete", "foo/obj")) { + log.Errorf("%v: expected true, got false", c) + } + } +} + +func TestDefaultRoleWithClaims(t *testing.T) { + kubeclientset := fake.NewSimpleClientset() + enf := rbac.NewEnforcer(kubeclientset, fakeNamespace, common.ArgoCDConfigMapName, nil) + enf.SetBuiltinPolicy(box.String(builtinPolicyFile)) + enf.SetClaimsEnforcerFunc(EnforceClaims(enf, nil, fakeNamespace)) + claims := jwt.MapClaims{"groups": []string{"org1:team1", "org2:team2"}} + + assert.False(t, enf.EnforceClaims(claims, "applications", "get", "foo/bar")) + // after setting the default role to be the read-only role, this should now pass + enf.SetDefaultRole("role:readonly") + assert.True(t, enf.EnforceClaims(claims, "applications", "get", "foo/bar")) +} + +func TestEnforceNilClaims(t *testing.T) { + kubeclientset := fake.NewSimpleClientset(fakeConfigMap()) + enf := rbac.NewEnforcer(kubeclientset, fakeNamespace, common.ArgoCDConfigMapName, nil) + enf.SetBuiltinPolicy(box.String(builtinPolicyFile)) + enf.SetClaimsEnforcerFunc(EnforceClaims(enf, nil, fakeNamespace)) + assert.False(t, enf.EnforceClaims(nil, "applications", "get", "foo/obj")) + enf.SetDefaultRole("role:readonly") + assert.True(t, enf.EnforceClaims(nil, "applications", "get", "foo/obj")) +} diff --git a/server/session/session.go b/server/session/session.go index 446ce72489701..6cb1bea8bea06 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -34,11 +34,11 @@ func (s *Server) Create(ctx context.Context, q *SessionCreateRequest) (*SessionR if err != nil { return nil, err } - tokenString, err := s.mgr.Create(q.Username, 0) + jwtToken, err := s.mgr.Create(q.Username, 0) if err != nil { return nil, err } - return &SessionResponse{Token: tokenString}, nil + return &SessionResponse{Token: jwtToken}, nil } // Delete an authentication cookie from the client. This makes sense only for the Web client. diff --git a/server/swagger.json b/server/swagger.json index 1185e7649629f..9ff607b887108 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -801,6 +801,74 @@ } } }, + "/api/v1/projects/{project}/roles/{role}/token": { + "post": { + "tags": [ + "ProjectService" + ], + "summary": "Create a new project token.", + "operationId": "CreateToken", + "parameters": [ + { + "type": "string", + "name": "project", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "role", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/projectProjectTokenCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "(empty)", + "schema": { + "$ref": "#/definitions/projectProjectTokenResponse" + } + } + } + }, + "delete": { + "tags": [ + "ProjectService" + ], + "summary": "Delete a new project token.", + "operationId": "DeleteToken", + "parameters": [ + { + "type": "string", + "name": "project", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "role", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "(empty)", + "schema": { + "$ref": "#/definitions/projectEmptyResponse" + } + } + } + } + }, "/api/v1/repositories": { "get": { "tags": [ @@ -1335,6 +1403,35 @@ } } }, + "projectProjectTokenCreateRequest": { + "description": "ProjectTokenCreateRequest defines project token creation parameters.", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "expiresIn": { + "type": "string", + "format": "int64", + "title": "expiresIn represents a duration in seconds" + }, + "project": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "projectProjectTokenResponse": { + "description": "ProjectTokenResponse wraps the created token or returns an empty string if deleted.", + "type": "object", + "properties": { + "token": { + "type": "string" + } + } + }, "projectProjectUpdateRequest": { "type": "object", "properties": { @@ -1876,6 +1973,12 @@ "$ref": "#/definitions/v1alpha1ApplicationDestination" } }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/v1alpha1ProjectRole" + } + }, "sourceRepos": { "type": "array", "title": "SourceRepos contains list of git repository URLs which can be used for deployment", @@ -2218,6 +2321,20 @@ } } }, + "v1alpha1JWTToken": { + "type": "object", + "title": "JWTToken holds the issuedAt and expiresAt values of a token", + "properties": { + "exp": { + "type": "string", + "format": "int64" + }, + "iat": { + "type": "string", + "format": "int64" + } + } + }, "v1alpha1Operation": { "description": "Operation contains requested operation parameters.", "type": "object", @@ -2259,6 +2376,31 @@ } } }, + "v1alpha1ProjectRole": { + "type": "object", + "title": "ProjectRole represents a role that has access to a project", + "properties": { + "description": { + "type": "string" + }, + "jwtTokens": { + "type": "array", + "items": { + "$ref": "#/definitions/v1alpha1JWTToken" + } + }, + "name": { + "type": "string" + }, + "policies": { + "description": "Policies Stores a list of casbin formated strings that define access policies for the role in the project.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, "v1alpha1Repository": { "type": "object", "title": "Repository is a Git repository holding application configurations", diff --git a/test/e2e/app_management_test.go b/test/e2e/app_management_test.go index 65b4e8e65ba4b..3c90b28521fa1 100644 --- a/test/e2e/app_management_test.go +++ b/test/e2e/app_management_test.go @@ -5,17 +5,16 @@ import ( "testing" "time" - "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - + "k8s.io/apimachinery/pkg/fields" // load the gcp plugin (required to authenticate against GKE clusters). _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/util/argo" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/fields" - _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" ) func TestAppManagement(t *testing.T) { diff --git a/test/e2e/fixture.go b/test/e2e/fixture.go index 8eb1906cf1b98..bc199f29afea8 100644 --- a/test/e2e/fixture.go +++ b/test/e2e/fixture.go @@ -259,6 +259,7 @@ func NewFixture() (*Fixture, error) { } db := db.NewDB(namespace, kubeClient) enforcer := rbac.NewEnforcer(kubeClient, namespace, common.ArgoCDRBACConfigMapName, nil) + enforcer.SetClaimsEnforcerFunc(server.EnforceClaims(enforcer, appClient, namespace)) err = enforcer.SetBuiltinPolicy(test.BuiltinPolicy) if err != nil { return nil, err diff --git a/test/e2e/project_management_test.go b/test/e2e/project_management_test.go index c60be26feb820..48734ebcf9bdb 100644 --- a/test/e2e/project_management_test.go +++ b/test/e2e/project_management_test.go @@ -6,12 +6,13 @@ import ( "testing" "time" - "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" - "github.com/argoproj/argo-cd/util/argo" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" + + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/util/argo" ) func TestProjectManagement(t *testing.T) { @@ -42,9 +43,7 @@ func TestProjectManagement(t *testing.T) { "-d", "https://192.168.99.100:8443,default", "-d", "https://192.168.99.100:8443,service", "-s", "https://github.com/argoproj/argo-cd.git") - if err != nil { - t.Fatalf("Unable to create project %v", err) - } + assert.Nil(t, err) proj, err := fixture.AppClient.ArgoprojV1alpha1().AppProjects(fixture.Namespace).Get(projectName, metav1.GetOptions{}) if err != nil { @@ -246,4 +245,40 @@ func TestProjectManagement(t *testing.T) { assert.Equal(t, 0, len(proj.Spec.SourceRepos)) assertProjHasEvent(proj, "update", argo.EventReasonResourceUpdated) }) + + t.Run("TestUseJWTToken", func(t *testing.T) { + projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10) + appName := "app-" + strconv.FormatInt(time.Now().Unix(), 10) + roleName := "roleTest" + testApp := &v1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: appName, + }, + Spec: v1alpha1.ApplicationSpec{ + Source: v1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argo-cd.git", Path: ".", Environment: "minikube", + }, + Destination: v1alpha1.ApplicationDestination{ + Server: fixture.Config.Host, + Namespace: fixture.Namespace, + }, + Project: projectName, + }, + } + _, err := fixture.AppClient.ArgoprojV1alpha1().AppProjects(fixture.Namespace).Create(&v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}}) + assert.Nil(t, err) + + _, err = fixture.AppClient.ArgoprojV1alpha1().Applications(fixture.Namespace).Create(testApp) + assert.Nil(t, err) + + _, err = fixture.RunCli("proj", "role", "create", projectName, roleName) + assert.Nil(t, err) + + _, err = fixture.RunCli("proj", "role", "create-token", projectName, roleName) + assert.Nil(t, err) + + _, err = fixture.RunCli("proj", "role", "add-policy", projectName, roleName, "-a", "get", "-o", "*", "-p", "allow") + assert.Nil(t, err) + + }) } diff --git a/util/project/util.go b/util/project/util.go new file mode 100644 index 0000000000000..aaf3c666b5f30 --- /dev/null +++ b/util/project/util.go @@ -0,0 +1,24 @@ +package project + +import "fmt" +import "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + +// GetRoleIndexByName looks up the index of a role in a project by the name +func GetRoleIndexByName(proj *v1alpha1.AppProject, name string) (int, error) { + for i, role := range proj.Spec.Roles { + if name == role.Name { + return i, nil + } + } + return -1, fmt.Errorf("role '%s' does not exist in project '%s'", name, proj.Name) +} + +// GetJWTTokenIndexByIssuedAt looks up the index of a JWTToken in a project by the issue at time +func GetJWTTokenIndexByIssuedAt(proj *v1alpha1.AppProject, roleIndex int, issuedAt int64) (int, error) { + for i, token := range proj.Spec.Roles[roleIndex].JWTTokens { + if issuedAt == token.IssuedAt { + return i, nil + } + } + return -1, fmt.Errorf("JWT token for role '%s' issued at '%d' does not exist in project '%s'", proj.Spec.Roles[roleIndex].Name, issuedAt, proj.Name) +} diff --git a/util/rbac/builtin-policy.csv b/util/rbac/builtin-policy.csv index 7acad3faf9d44..1a008c8b025f4 100644 --- a/util/rbac/builtin-policy.csv +++ b/util/rbac/builtin-policy.csv @@ -33,5 +33,6 @@ p, role:admin, projects, create, *, allow p, role:admin, projects, update, *, allow p, role:admin, projects, delete, *, allow + g, role:admin, role:readonly g, admin, role:admin diff --git a/util/rbac/rbac.go b/util/rbac/rbac.go index 0f45e3f32d565..57774d17825b5 100644 --- a/util/rbac/rbac.go +++ b/util/rbac/rbac.go @@ -7,7 +7,6 @@ import ( "github.com/casbin/casbin" "github.com/casbin/casbin/model" - jwt "github.com/dgrijalva/jwt-go" "github.com/gobuffalo/packr" scas "github.com/qiangmzsx/string-adapter" log "github.com/sirupsen/logrus" @@ -18,8 +17,6 @@ import ( v1 "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" - - jwtutil "github.com/argoproj/argo-cd/util/jwt" ) const ( @@ -47,10 +44,14 @@ type Enforcer struct { userDefinedPolicy string } -func NewEnforcer(clientset kubernetes.Interface, namespace, configmap string, claimsEnforcer ClaimsEnforcerFunc) *Enforcer { +func loadModel() model.Model { box := packr.NewBox(".") modelConf := box.String(builtinModelFile) - model := casbin.NewModel(modelConf) + return casbin.NewModel(modelConf) +} + +func NewEnforcer(clientset kubernetes.Interface, namespace, configmap string, claimsEnforcer ClaimsEnforcerFunc) *Enforcer { + model := loadModel() adapter := scas.NewAdapter("") enf := casbin.NewEnforcer(model, adapter) enf.EnableLog(false) @@ -90,42 +91,26 @@ func (e *Enforcer) Enforce(rvals ...interface{}) bool { return e.Enforcer.Enforce(rvals...) } +// EnforceCustomPolicy enforce a custom policy with the buildin and user defined policies in case of explicit deny of that resource +func (e *Enforcer) EnforceCustomPolicy(policy string, rvals ...interface{}) bool { + model := loadModel() + policies := fmt.Sprintf("%s\n%s\n%s", e.builtinPolicy, e.userDefinedPolicy, policy) + adapter := scas.NewAdapter(policies) + enf := casbin.NewEnforcer(model, adapter) + enf.EnableLog(false) + return enf.Enforce(rvals...) +} + // EnforceClaims checks if the first value is a jwt.Claims and runs enforce against its groups and sub func (e *Enforcer) EnforceClaims(rvals ...interface{}) bool { - // Use default claims enforcer if it is nil + // Return false if no enforcer is provided if e.claimsEnforcerFunc == nil { - return e.defaultEnforceClaims(rvals...) + return false } return e.claimsEnforcerFunc(rvals...) } -func (e *Enforcer) defaultEnforceClaims(rvals ...interface{}) bool { - claims, ok := rvals[0].(jwt.Claims) - if !ok { - if rvals[0] == nil { - vals := append([]interface{}{""}, rvals[1:]...) - return e.Enforce(vals...) - } - return e.Enforce(rvals...) - } - mapClaims, err := jwtutil.MapClaims(claims) - if err != nil { - vals := append([]interface{}{""}, rvals[1:]...) - return e.Enforce(vals...) - } - groups := jwtutil.GetGroups(mapClaims) - for _, group := range groups { - vals := append([]interface{}{group}, rvals[1:]...) - if e.Enforcer.Enforce(vals...) { - return true - } - } - user := jwtutil.GetField(mapClaims, "sub") - vals := append([]interface{}{user}, rvals[1:]...) - return e.Enforce(vals...) -} - // SetBuiltinPolicy sets a built-in policy, which augments any user defined policies func (e *Enforcer) SetBuiltinPolicy(policy string) error { e.builtinPolicy = policy diff --git a/util/rbac/rbac_test.go b/util/rbac/rbac_test.go index 5582e5f51e9e8..5f12b673844c2 100644 --- a/util/rbac/rbac_test.go +++ b/util/rbac/rbac_test.go @@ -5,12 +5,12 @@ import ( "testing" "time" - jwt "github.com/dgrijalva/jwt-go" "github.com/gobuffalo/packr" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" ) @@ -209,36 +209,6 @@ g, alice, role:foo-readonly assert.False(t, enf.Enforce("bob", "applications", "get", "foo/obj")) } -func TestEnforceClaims(t *testing.T) { - kubeclientset := fake.NewSimpleClientset(fakeConfigMap()) - enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil) - enf.SetBuiltinPolicy(box.String(builtinPolicyFile)) - policy := ` -g, org2:team2, role:admin -g, bob, role:admin -` - enf.SetUserPolicy(policy) - allowed := []jwt.Claims{ - jwt.MapClaims{"groups": []string{"org1:team1", "org2:team2"}}, - jwt.StandardClaims{Subject: "admin"}, - } - for _, c := range allowed { - if !assert.True(t, enf.EnforceClaims(c, "applications", "delete", "foo/obj")) { - log.Errorf("%v: expected true, got false", c) - } - } - - disallowed := []jwt.Claims{ - jwt.MapClaims{"groups": []string{"org3:team3"}}, - jwt.StandardClaims{Subject: "nobody"}, - } - for _, c := range disallowed { - if !assert.False(t, enf.EnforceClaims(c, "applications", "delete", "foo/obj")) { - log.Errorf("%v: expected true, got false", c) - } - } -} - // TestDefaultRole tests the ability to set a default role func TestDefaultRole(t *testing.T) { kubeclientset := fake.NewSimpleClientset() @@ -246,14 +216,11 @@ func TestDefaultRole(t *testing.T) { err := enf.syncUpdate(fakeConfigMap()) assert.Nil(t, err) enf.SetBuiltinPolicy(box.String(builtinPolicyFile)) - claims := jwt.MapClaims{"groups": []string{"org1:team1", "org2:team2"}} assert.False(t, enf.Enforce("bob", "applications", "get", "foo/bar")) - assert.False(t, enf.EnforceClaims(claims, "applications", "get", "foo/bar")) // after setting the default role to be the read-only role, this should now pass enf.SetDefaultRole("role:readonly") assert.True(t, enf.Enforce("bob", "applications", "get", "foo/bar")) - assert.True(t, enf.EnforceClaims(claims, "applications", "get", "foo/bar")) } // TestURLAsObjectName tests the ability to have a URL as an object name @@ -277,15 +244,6 @@ p, cathy, repositories, *, foo/*, allow } -func TestEnforceNilClaims(t *testing.T) { - kubeclientset := fake.NewSimpleClientset(fakeConfigMap()) - enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil) - enf.SetBuiltinPolicy(box.String(builtinPolicyFile)) - assert.False(t, enf.EnforceClaims(nil, "applications", "get", "foo/obj")) - enf.SetDefaultRole("role:readonly") - assert.True(t, enf.EnforceClaims(nil, "applications", "get", "foo/obj")) -} - func TestEnableDisableEnforce(t *testing.T) { kubeclientset := fake.NewSimpleClientset(fakeConfigMap()) enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfgMapName, nil) diff --git a/util/session/sessionmanager.go b/util/session/sessionmanager.go index 38b627dd1e8a2..cbcfc33c69a7f 100644 --- a/util/session/sessionmanager.go +++ b/util/session/sessionmanager.go @@ -72,7 +72,7 @@ func NewSessionManager(settings *settings.ArgoCDSettings) *SessionManager { // Create creates a new token for a given subject (user) and returns it as a string. // Passing a value of `0` for secondsBeforeExpiry creates a token that never expires. -func (mgr *SessionManager) Create(subject string, secondsBeforeExpiry int) (string, error) { +func (mgr *SessionManager) Create(subject string, secondsBeforeExpiry int64) (string, error) { // Create a new token object, specifying signing method and the claims // you would like it to contain. now := time.Now().UTC() @@ -86,6 +86,7 @@ func (mgr *SessionManager) Create(subject string, secondsBeforeExpiry int) (stri expires := now.Add(time.Duration(secondsBeforeExpiry) * time.Second) claims.ExpiresAt = expires.Unix() } + return mgr.signClaims(claims) } diff --git a/util/settings/settings.go b/util/settings/settings.go index 5a53ee53a6a72..b0089aa57b63c 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -11,11 +11,6 @@ import ( "syscall" "time" - "github.com/argoproj/argo-cd/common" - "github.com/argoproj/argo-cd/errors" - "github.com/argoproj/argo-cd/util" - "github.com/argoproj/argo-cd/util/password" - tlsutil "github.com/argoproj/argo-cd/util/tls" "github.com/ghodss/yaml" log "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" @@ -26,6 +21,12 @@ import ( "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" + + "github.com/argoproj/argo-cd/common" + "github.com/argoproj/argo-cd/errors" + "github.com/argoproj/argo-cd/util" + "github.com/argoproj/argo-cd/util/password" + tlsutil "github.com/argoproj/argo-cd/util/tls" ) // ArgoCDSettings holds in-memory runtime configuration options. @@ -123,6 +124,7 @@ func updateSettingsFromSecret(settings *ArgoCDSettings, argoCDSecret *apiv1.Secr settings.AdminPasswordMtime = adminPasswordMtime } } + secretKey, ok := argoCDSecret.Data[settingServerSignatureKey] if !ok { return fmt.Errorf("server secret key not found") @@ -200,6 +202,7 @@ func (mgr *SettingsManager) SaveSettings(settings *ArgoCDSettings) error { } createSecret = true } + argoCDSecret.StringData = make(map[string]string) argoCDSecret.StringData[settingServerSignatureKey] = string(settings.ServerSignature) argoCDSecret.StringData[settingAdminPasswordHashKey] = settings.AdminPasswordHash