Skip to content
Merged
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
2 changes: 1 addition & 1 deletion app/controlplane/cmd/wire_gen.go

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

15 changes: 7 additions & 8 deletions app/controlplane/configs/config.devel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ server:
# certificate: "../../devel/devkeys/selfsigned/controlplane.crt"
# private_key: "../../devel/devkeys/selfsigned/controlplane.key"

#nats_server:
# uri: nats://0.0.0.0:4222
# nats_server:
# uri: nats://0.0.0.0:4222

# Restrict organization creation to role:instance:admin
restrict_org_creation: true
restrict_org_creation: false
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fixing this on purpose


certificate_authorities:
- issuer: true
Expand Down Expand Up @@ -83,10 +83,10 @@ auth:
# Private key used to sign the JWTs meant to be consumed by the CAS
cas_robot_account_private_key_path: ${CAS_KEY_PATH:../../devel/devkeys/cas.pem}
# allow_list:
# rules: ["@chainloop.local"]
# selected_routes: ["/controlplane.v1.WorkflowRunService/List"]
# custom_message: "you need to require access here http://foo.com"
# allow_db_overrides: true # if false, the user access flag in the DB will mirror the allowlist, otherwise it will be respected
# rules: ["@chainloop.local"]
# selected_routes: ["/controlplane.v1.WorkflowRunService/List"]
# custom_message: "you need to require access here http://foo.com"
# allow_db_overrides: true # if false, the user access flag in the DB will mirror the allowlist, otherwise it will be respected

# referrer_shared_index:
# enabled: true
Expand All @@ -104,7 +104,6 @@ prometheus_integration:
# url: http://localhost:8002/v1

enable_profiler: true

# federated_authentication:
# enabled: true
# url: http://localhost:8002/machine-identity/verify-token
57 changes: 57 additions & 0 deletions app/controlplane/pkg/auditor/events/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ import (
)

var (
_ auditor.LogEntry = (*ProjectCreated)(nil)
_ auditor.LogEntry = (*ProjectVersionCreated)(nil)
_ auditor.LogEntry = (*ProjectMembershipAdded)(nil)
_ auditor.LogEntry = (*ProjectMembershipRemoved)(nil)
_ auditor.LogEntry = (*ProjectMemberRoleUpdated)(nil)
)

const (
ProjectType auditor.TargetType = "Project"
ProjectCreatedActionType string = "ProjectCreated"
ProjectVersionCreatedActionType string = "ProjectVersionCreated"
ProjectMembershipAddedActionType string = "ProjectMembershipAdded"
ProjectMembershipRemovedActionType string = "ProjectMembershipRemoved"
ProjectMemberRoleUpdatedType string = "ProjectMemberRoleUpdated"
Expand Down Expand Up @@ -65,6 +69,59 @@ func (p *ProjectBase) ActionInfo() (json.RawMessage, error) {
return json.Marshal(&p)
}

// ProjectCreated represents the creation of a project
type ProjectCreated struct {
*ProjectBase
}

func (p *ProjectCreated) ActionType() string {
return ProjectCreatedActionType
}

func (p *ProjectCreated) ActionInfo() (json.RawMessage, error) {
if _, err := p.ProjectBase.ActionInfo(); err != nil {
return nil, err
}

return json.Marshal(&p)
}

func (p *ProjectCreated) Description() string {
return fmt.Sprintf("%s has created the project '%s'", auditor.GetActorIdentifier(), p.ProjectName)
}

// ProjectVersionCreated represents the creation of a project version
type ProjectVersionCreated struct {
*ProjectBase
VersionID *uuid.UUID `json:"version_id,omitempty"`
Version string `json:"version,omitempty"`
Prerelease bool `json:"prerelease"`
}

func (p *ProjectVersionCreated) ActionType() string {
return ProjectVersionCreatedActionType
}

func (p *ProjectVersionCreated) ActionInfo() (json.RawMessage, error) {
if _, err := p.ProjectBase.ActionInfo(); err != nil {
return nil, err
}

if p.VersionID == nil || p.Version == "" {
return nil, errors.New("version id and version are required")
}

return json.Marshal(&p)
}

func (p *ProjectVersionCreated) Description() string {
releaseType := "release"
if p.Prerelease {
releaseType = "prerelease"
}
return fmt.Sprintf("%s has created %s version '%s' for project '%s'", auditor.GetActorIdentifier(), releaseType, p.Version, p.ProjectName)
}

// Helper function to make role names more user-friendly
func prettyRole(role string) string {
// Convert the role to a prettier format
Expand Down
29 changes: 29 additions & 0 deletions app/controlplane/pkg/auditor/events/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func TestProjectEvents(t *testing.T) {
require.NoError(t, err)
memberUUID, err := uuid.Parse("4089bb36-e27b-428b-8009-d015c8737c57")
require.NoError(t, err)
versionUUID, err := uuid.Parse("5089bb36-e27b-428b-8009-d015c8737c58")
require.NoError(t, err)
projectName := "test-project"
userEmail := "test@example.com"

Expand All @@ -49,6 +51,33 @@ func TestProjectEvents(t *testing.T) {
actor auditor.ActorType
actorID uuid.UUID
}{
{
name: "ProjectCreated",
event: &events.ProjectCreated{
ProjectBase: &events.ProjectBase{
ProjectID: &projectUUID,
ProjectName: projectName,
},
},
expected: "testdata/projects/project_created.json",
actor: auditor.ActorTypeUser,
actorID: userUUID,
},
{
name: "ProjectVersionCreated",
event: &events.ProjectVersionCreated{
ProjectBase: &events.ProjectBase{
ProjectID: &projectUUID,
ProjectName: projectName,
},
VersionID: &versionUUID,
Version: "v1.0.0",
Prerelease: false,
},
expected: "testdata/projects/project_version_created.json",
actor: auditor.ActorTypeUser,
actorID: userUUID,
},
{
name: "ProjectMembershipAdded",
event: &events.ProjectMembershipAdded{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"ActionType": "ProjectCreated",
"TargetType": "Project",
"TargetID": "3089bb36-e27b-428b-8009-d015c8737c56",
"ActorType": "USER",
"ActorID": "1089bb36-e27b-428b-8009-d015c8737c54",
"ActorEmail": "john@cyberdyne.io",
"ActorName": "John Connor",
"OrgID": "1089bb36-e27b-428b-8009-d015c8737c54",
"Description": "John Connor has created the project 'test-project'",
"Info": {
"project_id": "3089bb36-e27b-428b-8009-d015c8737c56",
"project_name": "test-project"
},
"Digest": "sha256:4d72f70e3f7311eecbb72451c71c6dceec3d38f8304cd1a064288148959a3991"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"ActionType": "ProjectVersionCreated",
"TargetType": "Project",
"TargetID": "3089bb36-e27b-428b-8009-d015c8737c56",
"ActorType": "USER",
"ActorID": "1089bb36-e27b-428b-8009-d015c8737c54",
"ActorEmail": "john@cyberdyne.io",
"ActorName": "John Connor",
"OrgID": "1089bb36-e27b-428b-8009-d015c8737c54",
"Description": "John Connor has created release version 'v1.0.0' for project 'test-project'",
"Info": {
"project_id": "3089bb36-e27b-428b-8009-d015c8737c56",
"project_name": "test-project",
"version_id": "5089bb36-e27b-428b-8009-d015c8737c58",
"version": "v1.0.0",
"prerelease": false
},
"Digest": "sha256:4cd962168303a29e0a3adb668b37086e50b15a92548d93a569eccf9a63db5122"
}
2 changes: 2 additions & 0 deletions app/controlplane/pkg/biz/auditor.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,14 @@ func (uc *AuditorUseCase) Dispatch(ctx context.Context, entry auditor.LogEntry,

payload, err := auditor.GenerateAuditEvent(entry, opts...)
if err != nil {
uc.log.Error("failed to generate audit event", "error", err)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the system was not logging auditing errors

sentry.CaptureException(fmt.Errorf("failed to generate audit event: %w", err))
return
}

// Send event to event bus
if err := uc.publisher.Publish(payload); err != nil {
uc.log.Error("failed to publish event", "error", err)
sentry.CaptureException(fmt.Errorf("failed to publish event: %w", err))
}
}
10 changes: 5 additions & 5 deletions app/controlplane/pkg/biz/mocks/WorkflowRunRepo.go

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

15 changes: 14 additions & 1 deletion app/controlplane/pkg/biz/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,20 @@ func (uc *ProjectUseCase) Create(ctx context.Context, orgID, name string) (*Proj
return nil, NewErrInvalidUUID(err)
}

return uc.projectsRepository.Create(ctx, orgUUID, name)
project, err := uc.projectsRepository.Create(ctx, orgUUID, name)
if err != nil {
return nil, err
}

// Dispatch audit event for project creation
uc.auditorUC.Dispatch(ctx, &events.ProjectCreated{
ProjectBase: &events.ProjectBase{
ProjectID: &project.ID,
ProjectName: project.Name,
},
}, &orgUUID)

return project, nil
}

// ListMembers lists the members of a project with pagination.
Expand Down
5 changes: 3 additions & 2 deletions app/controlplane/pkg/biz/projectversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type ProjectVersion struct {
CreatedAt *time.Time
// ReleasedAt is the time when the version was released.
ReleasedAt *time.Time
ProjectID uuid.UUID
}

type ProjectVersionRepo interface {
Expand Down Expand Up @@ -78,8 +79,8 @@ func (uc *ProjectVersionUseCase) UpdateReleaseStatus(ctx context.Context, versio
return nil, NewErrInvalidUUID(err)
}

preRelease := !isRelease
return uc.projectRepo.Update(ctx, versionUUID, &ProjectVersionUpdateOpts{Prerelease: &preRelease})
preReleaseValue := !isRelease
return uc.projectRepo.Update(ctx, versionUUID, &ProjectVersionUpdateOpts{Prerelease: &preReleaseValue})
}

func (uc *ProjectVersionUseCase) Create(ctx context.Context, projectID, version string, prerelease bool) (*ProjectVersion, error) {
Expand Down
2 changes: 1 addition & 1 deletion app/controlplane/pkg/biz/testhelpers/wire_gen.go

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

63 changes: 36 additions & 27 deletions app/controlplane/pkg/biz/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ func (uc *WorkflowUseCase) Create(ctx context.Context, opts *WorkflowCreateOpts)
return nil, NewErrValidation(err)
}

// Check if the project already exists to determine if we need to emit a ProjectCreated event
orgUUID, err := uuid.Parse(opts.OrgID)
if err != nil {
return nil, fmt.Errorf("failed to parse org ID %q: %w", opts.OrgID, err)
}

existingProject, _ := uc.projectRepo.FindProjectByOrgIDAndName(ctx, orgUUID, opts.Project)

wf, err := uc.wfRepo.Create(ctx, opts)
if err != nil {
if IsErrAlreadyExists(err) {
Expand All @@ -166,43 +174,44 @@ func (uc *WorkflowUseCase) Create(ctx context.Context, opts *WorkflowCreateOpts)

// Set project admin if a new project has been created
if opts.Owner != nil {
orgUUID, err := uuid.Parse(opts.OrgID)
if err != nil {
return nil, fmt.Errorf("failed to parse org ID %q: %w", opts.OrgID, err)
}
if err = uc.membershipUC.SetProjectOwner(ctx, orgUUID, wf.ProjectID, *opts.Owner); err != nil {
return nil, fmt.Errorf("failed to set project owner: %w", err)
}
}

orgUUID, err := uuid.Parse(opts.OrgID)
if err != nil {
uc.logger.Warn("failed to parse org id", "err", err)
} else {
// Dispatch events to the audit log regarding the workflow
uc.auditorUC.Dispatch(ctx, &events.WorkflowCreated{
WorkflowBase: &events.WorkflowBase{
WorkflowID: &wf.ID,
WorkflowName: wf.Name,
ProjectName: opts.Project,
// Dispatch audit event for project creation if a new project was created
if existingProject == nil {
uc.auditorUC.Dispatch(ctx, &events.ProjectCreated{
ProjectBase: &events.ProjectBase{
ProjectID: &wf.ProjectID,
ProjectName: opts.Project,
},
WorkflowContractID: &wf.ContractID,
WorkflowContractName: wf.ContractName,
WorkflowDescription: &opts.Description,
Team: &opts.Team,
Public: opts.Public,
}, &orgUUID)
}

// Dispatch events to the audit log regarding the contract
uc.auditorUC.Dispatch(ctx, &events.WorkflowContractAttached{
WorkflowContractBase: &events.WorkflowContractBase{
WorkflowContractID: &wf.ContractID,
WorkflowContractName: wf.ContractName,
},
// Dispatch events to the audit log regarding the workflow
uc.auditorUC.Dispatch(ctx, &events.WorkflowCreated{
WorkflowBase: &events.WorkflowBase{
WorkflowID: &wf.ID,
WorkflowName: wf.Name,
}, &orgUUID)
}
ProjectName: opts.Project,
},
WorkflowContractID: &wf.ContractID,
WorkflowContractName: wf.ContractName,
WorkflowDescription: &opts.Description,
Team: &opts.Team,
Public: opts.Public,
}, &orgUUID)

// Dispatch events to the audit log regarding the contract
uc.auditorUC.Dispatch(ctx, &events.WorkflowContractAttached{
WorkflowContractBase: &events.WorkflowContractBase{
WorkflowContractID: &wf.ContractID,
WorkflowContractName: wf.ContractName,
},
WorkflowID: &wf.ID,
WorkflowName: wf.Name,
}, &orgUUID)

return wf, nil
}
Expand Down
Loading
Loading