Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: permission for connection #1831

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions api/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
gocontext "context"
"database/sql/driver"

"github.com/flanksource/duty/context"
"github.com/flanksource/duty/types"
"gorm.io/gorm"
"gorm.io/gorm/clause"
Expand Down Expand Up @@ -42,16 +41,3 @@ func (t NotificationConfig) GormDBDataType(db *gorm.DB, field *schema.Field) str
func (t NotificationConfig) GormValue(ctx gocontext.Context, db *gorm.DB) clause.Expr {
return types.GormValue(t)
}

func (t *NotificationConfig) HydrateConnection(ctx context.Context) error {
connection, err := ctx.HydrateConnectionByURL(t.Connection)
if err != nil {
return err
} else if connection == nil {
return nil
}

t.URL = connection.URL

return nil
}
33 changes: 28 additions & 5 deletions api/v1/permission_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (t PermissionSubjectSelector) Find(ctx context.Context, table string) (stri

splits := strings.Split(string(t), "/")
if len(splits) != 2 {
return "", "", fmt.Errorf("%s is not a valid notification subject. must be <namespace>/<name>", t)
return "", "", fmt.Errorf("%s is not a valid notification subject. Must be <namespace>/<name>", t)
}

namespace, name := splits[0], splits[1]
Expand All @@ -78,9 +78,24 @@ func (t PermissionSubjectSelector) Find(ctx context.Context, table string) (stri
Where("name = ?", name).
Find(&id).Error
return id, models.PermissionSubjectTypeNotification, err
}

return "", "", nil
case "playbooks":
splits := strings.Split(string(t), "/")
if len(splits) != 2 {
return "", "", fmt.Errorf("%s is not a valid playbooks subject. Must be <namespace>/<name>", t)
}

namespace, name := splits[0], splits[1]
var id string
err := ctx.DB().Select("id").Table(table).
Where("namespace = ?", namespace).
Where("name = ?", name).
Find(&id).Error
return id, models.PermissionSubjectTypePlaybook, err

default:
return "", "", fmt.Errorf("unknown table: %v", table)
}
}

type PermissionSubject struct {
Expand All @@ -95,11 +110,16 @@ type PermissionSubject struct {

// Group is the group name
Group string `json:"group,omitempty"`

Playbook PermissionSubjectSelector `json:"playbook,omitempty"`
Canary PermissionSubjectSelector `json:"canary,omitempty"`
Scraper PermissionSubjectSelector `json:"scraper,omitempty"`
Topology PermissionSubjectSelector `json:"topology,omitempty"`
}

func (t *PermissionSubject) Validate() error {
if t.Person == "" && t.Team == "" && t.Notification == "" && t.Group == "" {
return errors.New("subject is empty: one of person, team, notification or a group is required")
if t.Person == "" && t.Team == "" && t.Notification == "" && t.Group == "" && t.Playbook == "" {
return errors.New("subject is empty: one of person, team, playbook, notification or a group is required")
}

return nil
Expand All @@ -122,6 +142,9 @@ func (t *PermissionSubject) Populate(ctx context.Context) (string, models.Permis
if t.Group != "" {
return string(t.Group), models.PermissionSubjectTypeGroup, nil
}
if t.Playbook != "" {
return t.Playbook.Find(ctx, "playbooks")
}

return "", "", errors.New("subject not found")
}
Expand Down
21 changes: 7 additions & 14 deletions api/v1/playbook_actions.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package v1

import (
gocontext "context"
"encoding/json"
"fmt"
"time"
Expand Down Expand Up @@ -266,7 +265,7 @@ type AIActionClient struct {

func (t *AIActionClient) Populate(ctx context.Context) error {
if t.Connection != nil {
conn, err := ctx.HydrateConnectionByURL(*t.Connection)
conn, err := connection.Get(ctx, *t.Connection)
if err != nil {
return err
} else if conn == nil {
Expand Down Expand Up @@ -396,12 +395,6 @@ func (e *ExecAction) ToShellExec() shell.Exec {
}
}

type connectionContext interface {
gocontext.Context
HydrateConnectionByURL(connectionName string) (*models.Connection, error)
GetEnvValueFromCache(env types.EnvVar, namespace string) (string, error)
}

type GCPConnection struct {
// ConnectionName of the connection. It'll be used to populate the endpoint and credentials.
ConnectionName string `yaml:"connection,omitempty" json:"connection,omitempty"`
Expand All @@ -411,8 +404,8 @@ type GCPConnection struct {

// HydrateConnection attempts to find the connection by name
// and populate the endpoint and credentials.
func (g *GCPConnection) HydrateConnection(ctx connectionContext) error {
connection, err := ctx.HydrateConnectionByURL(g.ConnectionName)
func (g *GCPConnection) HydrateConnection(ctx context.Context) error {
connection, err := connection.Get(ctx, g.ConnectionName)
if err != nil {
return err
}
Expand All @@ -434,8 +427,8 @@ type AzureConnection struct {

// HydrateConnection attempts to find the connection by name
// and populate the endpoint and credentials.
func (g *AzureConnection) HydrateConnection(ctx connectionContext) error {
connection, err := ctx.HydrateConnectionByURL(g.ConnectionName)
func (g *AzureConnection) HydrateConnection(ctx context.Context) error {
connection, err := connection.Get(ctx, g.ConnectionName)
if err != nil {
return err
}
Expand Down Expand Up @@ -467,9 +460,9 @@ type AWSConnection struct {

// Populate populates an AWSConnection with credentials and other information.
// If a connection name is specified, it'll be used to populate the endpoint, accessKey and secretKey.
func (t *AWSConnection) Populate(ctx connectionContext, k8s kubernetes.Interface, namespace string) error {
func (t *AWSConnection) Populate(ctx context.Context, k8s kubernetes.Interface, namespace string) error {
if t.ConnectionName != "" {
connection, err := ctx.HydrateConnectionByURL(t.ConnectionName)
connection, err := connection.Get(ctx, t.ConnectionName)
if err != nil {
return fmt.Errorf("could not parse EC2 access key: %v", err)
}
Expand Down
7 changes: 7 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions artifacts/artifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ import (

"github.com/flanksource/artifacts"
dutyAPI "github.com/flanksource/duty/api"
pkgConnection "github.com/flanksource/duty/connection"
"github.com/flanksource/duty/context"
"github.com/flanksource/duty/models"
"github.com/flanksource/incident-commander/api"
"github.com/google/uuid"
"gorm.io/gorm"

"github.com/flanksource/incident-commander/api"
)

// UploadArtifact Uploads the given artifact data to the default artifact store
func UploadArtifact(ctx context.Context, artifactID string, reader io.ReadCloser) error {
if _, err := uuid.Parse(artifactID); err != nil {
return dutyAPI.Errorf(dutyAPI.EINVALID, fmt.Sprintf("(%s) is not a valid uuid", artifactID))
return dutyAPI.Errorf(dutyAPI.EINVALID, "(%s) is not a valid uuid", artifactID)
}

var artifact models.Artifact
Expand All @@ -29,7 +31,7 @@ func UploadArtifact(ctx context.Context, artifactID string, reader io.ReadCloser
return fmt.Errorf("failed to get artifact: %w", err)
}

conn, err := ctx.HydrateConnectionByURL(api.DefaultArtifactConnection)
conn, err := pkgConnection.Get(ctx, api.DefaultArtifactConnection)
if err != nil {
return fmt.Errorf("failed to get connection: %w", err)
} else if conn == nil {
Expand Down
3 changes: 2 additions & 1 deletion artifacts/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/flanksource/artifacts"
"github.com/flanksource/commons/logger"
"github.com/flanksource/duty/api"
pkgConnection "github.com/flanksource/duty/connection"
"github.com/flanksource/duty/context"
"github.com/flanksource/duty/models"
"github.com/flanksource/duty/query"
Expand Down Expand Up @@ -82,7 +83,7 @@ func DownloadArtifact(c echo.Context) error {
return api.WriteError(c, api.Errorf(api.ENOTFOUND, "artifact(%s) was not found", artifactID))
}

conn, err := ctx.HydrateConnectionByURL(artifact.ConnectionID.String())
conn, err := pkgConnection.Get(ctx, artifact.ConnectionID.String())
if err != nil {
return api.WriteError(c, err)
} else if conn == nil {
Expand Down
6 changes: 4 additions & 2 deletions artifacts/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import (

"github.com/flanksource/artifacts"
"github.com/flanksource/artifacts/fs"
pkgConnection "github.com/flanksource/duty/connection"
"github.com/flanksource/duty/context"
"github.com/flanksource/duty/models"
"github.com/flanksource/duty/upstream"
"github.com/flanksource/incident-commander/api"
"gorm.io/gorm/clause"

"github.com/flanksource/incident-commander/api"
)

// agentArtifactConnection is the cached agent artifact store connection
var agentArtifactConnection *models.Connection

func getArtifactStore(ctx context.Context) (fs.FilesystemRW, error) {
if agentArtifactConnection == nil {
artifactConnection, err := ctx.HydrateConnectionByURL(api.DefaultArtifactConnection)
artifactConnection, err := pkgConnection.Get(ctx, api.DefaultArtifactConnection)
if err != nil {
return nil, err
} else if artifactConnection == nil {
Expand Down
92 changes: 92 additions & 0 deletions config/crds/mission-control.flanksource.com_permissions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,66 @@ spec:
type: array
type: object
type: array
connections:
items:
properties:
agent:
description: |-
Agent can be the agent id or the name of the agent.
Additionally, the special "self" value can be used to select resources without an agent.
type: string
cache:
description: |-
Cache directives
'no-cache' (should not fetch from cache but can be cached)
'no-store' (should not cache)
'max-age=X' (cache for X duration)
type: string
fieldSelector:
type: string
health:
description: |-
Health filters resources by the health.
Multiple healths can be provided separated by comma.
type: string
id:
type: string
includeDeleted:
type: boolean
labelSelector:
type: string
limit:
type: integer
name:
type: string
namespace:
type: string
scope:
description: |-
Scope is the reference for parent of the resource to select.
For config items, the scope is the scraper id
For checks, it's canaries and
For components, it's topology.
It can either be a uuid or namespace/name
type: string
search:
description: Search query that applies to the resource name,
tag & labels.
type: string
statuses:
description: Statuses filter resources by the status
items:
type: string
type: array
tagSelector:
type: string
types:
description: Types filter resources by the type
items:
type: string
type: array
type: object
type: array
playbooks:
items:
properties:
Expand Down Expand Up @@ -250,6 +310,14 @@ spec:
description: Subject defines the entity (e.g., user, group) to which
the permission applies.
properties:
canary:
description: |-
Subject of the permission.
Can be in various formats depending on the table
- id of a resource
- name or email for person
- <namespace>/<name> of a notification
type: string
group:
description: Group is the group name
type: string
Expand All @@ -259,9 +327,33 @@ spec:
person:
description: ID or email of the person
type: string
playbook:
description: |-
Subject of the permission.
Can be in various formats depending on the table
- id of a resource
- name or email for person
- <namespace>/<name> of a notification
type: string
scraper:
description: |-
Subject of the permission.
Can be in various formats depending on the table
- id of a resource
- name or email for person
- <namespace>/<name> of a notification
type: string
team:
description: Team is the team name
type: string
topology:
description: |-
Subject of the permission.
Can be in various formats depending on the table
- id of a resource
- name or email for person
- <namespace>/<name> of a notification
type: string
type: object
tags:
additionalProperties:
Expand Down
Loading
Loading