-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'feature-rebac' into CSS-4936-postgres-as-secret-backend
- Loading branch information
Showing
11 changed files
with
335 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,5 +18,5 @@ local/vault/roleid.txt | |
*.crt | ||
*.key | ||
*.csr | ||
jimmctl | ||
/jimmctl | ||
qa-controller |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package cmd | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/canonical/jimm/api" | ||
apiparams "github.com/canonical/jimm/api/params" | ||
"github.com/canonical/jimm/internal/errors" | ||
"github.com/juju/cmd/v3" | ||
"github.com/juju/gnuflag" | ||
jujuapi "github.com/juju/juju/api" | ||
jujucmd "github.com/juju/juju/cmd" | ||
"github.com/juju/juju/cmd/modelcmd" | ||
"github.com/juju/juju/jujuclient" | ||
) | ||
|
||
const purgeLogsDoc = ` | ||
purge-audit-logs purges logs from the database before the given date. | ||
Examples: | ||
jimmctl purge-audit-logs 2021-02-03 | ||
jimmctl purge-audit-logs 2021-02-03T00 | ||
jimmctl purge-audit-logs 2021-02-03T15:04:05Z | ||
` | ||
|
||
// NewPurgeLogsCommand returns a command to purge logs. | ||
func NewPurgeLogsCommand() cmd.Command { | ||
cmd := &purgeLogsCommand{} | ||
return modelcmd.WrapBase(cmd) | ||
} | ||
|
||
// purgeLogsCommand purges logs. | ||
type purgeLogsCommand struct { | ||
modelcmd.ControllerCommandBase | ||
store jujuclient.ClientStore | ||
dialOpts *jujuapi.DialOpts | ||
out cmd.Output | ||
|
||
date time.Time | ||
} | ||
|
||
// Info implements Command.Info. It returns the command information. | ||
func (c *purgeLogsCommand) Info() *cmd.Info { | ||
return jujucmd.Info(&cmd.Info{ | ||
Name: "purge-audit-logs", | ||
Args: "<ISO8601 date>", | ||
Purpose: "purges audit logs from the database before the given date", | ||
Doc: purgeLogsDoc, | ||
}) | ||
} | ||
|
||
// Init implements Command.Init. It checks the number of arguments and validates | ||
// the date. | ||
func (c *purgeLogsCommand) Init(args []string) error { | ||
if len(args) != 1 { | ||
return errors.E("expected one argument (ISO8601 date)") | ||
} | ||
// validate date | ||
var err error | ||
c.date, err = parseDate(args[0]) | ||
if err != nil { | ||
return errors.E("invalid date. Expected ISO8601 date") | ||
} | ||
return nil | ||
} | ||
|
||
// SetFlags implements Command.SetFlags. | ||
func (c *purgeLogsCommand) SetFlags(f *gnuflag.FlagSet) { | ||
c.CommandBase.SetFlags(f) | ||
c.out.AddFlags(f, "yaml", map[string]cmd.Formatter{ | ||
"yaml": cmd.FormatYaml, | ||
"json": cmd.FormatJson, | ||
}) | ||
} | ||
|
||
// Run implements Command.Run. It purges logs from the database before the given | ||
// date. | ||
func (c *purgeLogsCommand) Run(ctx *cmd.Context) error { | ||
currentController, err := c.store.CurrentController() | ||
if err != nil { | ||
return errors.E(err, "could not determine controller") | ||
} | ||
|
||
apiCaller, err := c.NewAPIRootWithDialOpts(c.store, currentController, "", c.dialOpts) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
client := api.NewClient(apiCaller) | ||
response, err := client.PurgeLogs(&apiparams.PurgeLogsRequest{ | ||
Date: c.date, | ||
}) | ||
if err != nil { | ||
return errors.E(err) | ||
} | ||
err = c.out.Write(ctx, response) | ||
if err != nil { | ||
return errors.E(err) | ||
} | ||
return nil | ||
} | ||
|
||
// parseDate validates the date string is in ISO8601 format. If it is, it | ||
// sets the date field in the command. | ||
func parseDate(date string) (time.Time, error) { | ||
// Define the possible ISO8601 date layouts | ||
layouts := []string{ | ||
"2006-01-02T15:04:05-0700", | ||
"2006-01-02T15:04:05Z", | ||
"2006-01-02T15:04:05", | ||
"2006-01-02T15:04Z", | ||
"2006-01-02", | ||
} | ||
|
||
// Try to parse the date string using the defined layouts | ||
for _, layout := range layouts { | ||
date_time, err := time.Parse(layout, date) | ||
if err == nil { | ||
// If parsing was successful, the date is valid | ||
// You can use the parsed time t if needed | ||
return date_time, nil | ||
} | ||
} | ||
|
||
// If none of the layouts match, the date is not in the correct format | ||
return time.Time{}, errors.E("invalid date. Expected ISO8601 date") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package cmd_test | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"time" | ||
|
||
"github.com/canonical/jimm/cmd/jimmctl/cmd" | ||
"github.com/canonical/jimm/internal/dbmodel" | ||
"github.com/juju/cmd/v3/cmdtesting" | ||
"github.com/juju/names/v4" | ||
gc "gopkg.in/check.v1" | ||
) | ||
|
||
type purgeLogsSuite struct { | ||
jimmSuite | ||
} | ||
|
||
var _ = gc.Suite(&purgeLogsSuite{}) | ||
|
||
func (s *purgeLogsSuite) TestPurgeLogsSuperuser(c *gc.C) { | ||
// alice is superuser | ||
bClient := s.userBakeryClient("alice") | ||
datastring := "2021-01-01T00:00:00Z" | ||
cmdCtx, err := cmdtesting.RunCommand(c, cmd.NewPurgeLogsCommandForTesting(s.ClientStore(), bClient), datastring) | ||
c.Assert(err, gc.IsNil) | ||
expected := "deleted-count: 0\n" | ||
actual := cmdCtx.Stdout.(*bytes.Buffer).String() | ||
c.Assert(actual, gc.Equals, expected) | ||
} | ||
|
||
func (s *purgeLogsSuite) TestInvalidISO8601Date(c *gc.C) { | ||
// alice is superuser | ||
bClient := s.userBakeryClient("alice") | ||
datastring := "13/01/2021" | ||
_, err := cmdtesting.RunCommand(c, cmd.NewPurgeLogsCommandForTesting(s.ClientStore(), bClient), datastring) | ||
c.Assert(err, gc.ErrorMatches, `invalid date. Expected ISO8601 date`) | ||
|
||
} | ||
|
||
func (s *purgeLogsSuite) TestPurgeLogs(c *gc.C) { | ||
// bob is not superuser | ||
bClient := s.userBakeryClient("bob") | ||
_, err := cmdtesting.RunCommand(c, cmd.NewPurgeLogsCommandForTesting(s.ClientStore(), bClient), "2021-01-01T00:00:00Z") | ||
c.Assert(err, gc.ErrorMatches, `unauthorized \(unauthorized access\)`) | ||
} | ||
|
||
func (s *purgeLogsSuite) TestPurgeLogsFromDb(c *gc.C) { | ||
// create logs | ||
layouts := []string{ | ||
"2006-01-02T15:04:05-0700", | ||
"2006-01-02T15:04:05Z", | ||
"2006-01-02T15:04:05", | ||
"2006-01-02T15:04Z", | ||
"2006-01-02", | ||
} | ||
for _, layout := range layouts { | ||
|
||
ctx := context.Background() | ||
relativeNow := time.Now().AddDate(-1, 0, 0) | ||
ale := dbmodel.AuditLogEntry{ | ||
Time: relativeNow.UTC().Round(time.Millisecond), | ||
UserTag: names.NewUserTag("alice@external").String(), | ||
} | ||
ale_past := dbmodel.AuditLogEntry{ | ||
Time: relativeNow.AddDate(0, 0, -1).UTC().Round(time.Millisecond), | ||
UserTag: names.NewUserTag("alice@external").String(), | ||
} | ||
ale_future := dbmodel.AuditLogEntry{ | ||
Time: relativeNow.AddDate(0, 0, 5).UTC().Round(time.Millisecond), | ||
UserTag: names.NewUserTag("alice@external").String(), | ||
} | ||
|
||
err := s.JIMM.Database.Migrate(context.Background(), false) | ||
c.Assert(err, gc.IsNil) | ||
err = s.JIMM.Database.AddAuditLogEntry(ctx, &ale) | ||
c.Assert(err, gc.IsNil) | ||
err = s.JIMM.Database.AddAuditLogEntry(ctx, &ale_past) | ||
c.Assert(err, gc.IsNil) | ||
err = s.JIMM.Database.AddAuditLogEntry(ctx, &ale_future) | ||
c.Assert(err, gc.IsNil) | ||
|
||
tomorrow := relativeNow.AddDate(0, 0, 1).Format(layout) | ||
//alice is superuser | ||
bClient := s.userBakeryClient("alice") | ||
cmdCtx, err := cmdtesting.RunCommand(c, cmd.NewPurgeLogsCommandForTesting(s.ClientStore(), bClient), tomorrow) | ||
c.Assert(err, gc.IsNil) | ||
// check that logs have been deleted | ||
expectedOutput := "deleted-count: 2\n" | ||
actual := cmdCtx.Stdout.(*bytes.Buffer).String() | ||
c.Assert(actual, gc.Equals, expectedOutput) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright 2023 Canonical Ltd. | ||
|
||
package jimm | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/canonical/jimm/internal/errors" | ||
"github.com/canonical/jimm/internal/openfga" | ||
"github.com/juju/zaputil/zapctx" | ||
"go.uber.org/zap" | ||
) | ||
|
||
// PurgeLogs removes all audit logs before the given timestamp. Only JIMM | ||
// administrators can perform this operation. The number of logs purged is | ||
// returned. | ||
func (j *JIMM) PurgeLogs(ctx context.Context, user *openfga.User, before time.Time) (int64, error) { | ||
op := errors.Op("jimm.PurgeLogs") | ||
isJIMMAdmin, err := openfga.IsAdministrator(ctx, user, j.ResourceTag()) | ||
if err != nil { | ||
zapctx.Error(ctx, "failed administrator check", zap.Error(err)) | ||
return 0, errors.E(op, "failed administrator check", err) | ||
} | ||
if !isJIMMAdmin { | ||
return 0, errors.E(op, errors.CodeUnauthorized, "unauthorized") | ||
} | ||
count, err := j.Database.DeleteAuditLogsBefore(ctx, before) | ||
if err != nil { | ||
zapctx.Error(ctx, "failed to purge logs", zap.Error(err)) | ||
return 0, errors.E(op, "failed to purge logs", err) | ||
} | ||
return count, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters