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

Add Audit Trail/Logging #24257

Open
wants to merge 86 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
afcb22b
Add audit event logging.
KN4CK3R Apr 17, 2023
204b114
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Apr 18, 2023
2dcff01
Added file logging.
KN4CK3R Apr 18, 2023
9d7b3c8
Fixes.
KN4CK3R Apr 21, 2023
6d7835a
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Apr 21, 2023
6e6305d
Added documentation.
KN4CK3R Apr 21, 2023
f36f208
Lint docs.
KN4CK3R Apr 21, 2023
8939539
Fixed typo.
KN4CK3R Apr 21, 2023
7131cf6
Remove errors.Join.
KN4CK3R Apr 21, 2023
f732ae6
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Apr 21, 2023
0f56aa1
Fix merge error.
KN4CK3R Apr 21, 2023
8038aee
Update models/db/context.go
KN4CK3R Apr 23, 2023
fb3f954
Change method name.
KN4CK3R Apr 23, 2023
b3779fc
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Apr 23, 2023
c7e989a
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Apr 24, 2023
659c324
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R May 7, 2023
3c613b3
Remove notice appender.
KN4CK3R May 7, 2023
11ddf58
Use errors.Join.
KN4CK3R May 7, 2023
033c8fe
lint
KN4CK3R May 7, 2023
777362e
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R May 16, 2023
3684f3a
Fix queue merge error.
KN4CK3R May 16, 2023
7e474a6
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R May 22, 2023
5615970
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R May 27, 2023
9e7082b
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Jun 12, 2023
3d1f881
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Jun 25, 2023
2c01fe2
Merge branch 'main' into feature-audit
KN4CK3R Jun 30, 2023
ab956ba
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Jul 9, 2023
073603f
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Aug 6, 2023
718f1a9
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Aug 14, 2023
f69976e
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Aug 30, 2023
65e4f7c
Add tests.
KN4CK3R Aug 31, 2023
d1b5a22
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Sep 5, 2023
59aa27f
Apply suggestions.
KN4CK3R Sep 5, 2023
1294a00
Merge tests.
KN4CK3R Sep 5, 2023
d2930ec
Apply suggestions from code review
KN4CK3R Sep 6, 2023
bf5f725
Fix format string.
KN4CK3R Sep 6, 2023
d1445eb
Change log message.
KN4CK3R Sep 6, 2023
6b0b2f7
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Sep 15, 2023
8d21f95
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Sep 16, 2023
1a28a97
Use new PushMirror.RemoteAddress.
KN4CK3R Sep 16, 2023
5d51639
Add some suggestions.
KN4CK3R Sep 22, 2023
001727e
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Sep 22, 2023
4c6eabe
Change sudo target.
KN4CK3R Sep 22, 2023
ec6ae2f
Remove ReleaseReopen.
KN4CK3R Sep 22, 2023
10fed1c
Remove todo.
KN4CK3R Sep 22, 2023
bbb0322
Use existing cron user.
KN4CK3R Sep 23, 2023
c95c03f
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Sep 23, 2023
d153e4d
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Oct 13, 2023
1fa5edd
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 5, 2023
989fb36
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 12, 2023
41287b9
Add ip address to audit log.
KN4CK3R Nov 12, 2023
c5ff607
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 16, 2023
d56fae1
Simplify appender.
KN4CK3R Nov 16, 2023
20d6fdd
Add database logging.
KN4CK3R Nov 19, 2023
8c64051
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 19, 2023
3b945d9
Display audit logs in admin/org/repo settings.
KN4CK3R Nov 21, 2023
e928e40
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 21, 2023
af5b9b6
fix lint
KN4CK3R Nov 21, 2023
9885036
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Nov 26, 2023
fa8d530
lint
KN4CK3R Nov 26, 2023
95682a3
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Dec 28, 2023
ec55854
Add some suggestions.
KN4CK3R Dec 29, 2023
0de61c3
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Feb 6, 2024
6fffaac
Use individual methods.
KN4CK3R Feb 6, 2024
278ca4b
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Feb 7, 2024
3b1336a
Log system startup/shutdown.
KN4CK3R Feb 8, 2024
d090810
Add audit logs to user settings.
KN4CK3R Feb 8, 2024
558fd1a
Update docs.
KN4CK3R Feb 8, 2024
bcd6f99
Merge branch 'main' into feature-audit
delvh Feb 18, 2024
ac2b8f3
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Feb 24, 2024
745be63
Add suggestions.
KN4CK3R Feb 24, 2024
3b7298b
Merge branch 'main' into feature-audit
6543 Feb 25, 2024
cfebe22
Apply suggestions from code review
KN4CK3R Mar 1, 2024
b813aba
Fix migration.
KN4CK3R Mar 2, 2024
59f1537
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Mar 2, 2024
4e41c8b
Use `ConfigSectionKeyBool`.
KN4CK3R Mar 2, 2024
c4d5fd2
Fix context imports.
KN4CK3R Mar 2, 2024
d6fd348
Fix imports.
KN4CK3R Mar 2, 2024
12dc51c
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Mar 4, 2024
3a919d2
Fix typo.
KN4CK3R Mar 4, 2024
9482ee0
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Apr 21, 2024
7ae645e
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R May 14, 2024
a68292b
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Jun 25, 2024
00359c7
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Aug 7, 2024
694ba9b
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Aug 7, 2024
4644d76
Merge branch 'main' of https://github.com/go-gitea/gitea into feature…
KN4CK3R Dec 3, 2024
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
36 changes: 31 additions & 5 deletions cmd/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/audit"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/auth/source/smtp"
Expand Down Expand Up @@ -480,6 +481,7 @@ func runAddOauth(c *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}
audit.Init()

config := parseOAuth2Config(c)
if config.Provider == "openidConnect" {
Expand All @@ -489,7 +491,7 @@ func runAddOauth(c *cli.Context) error {
}
}

return auth_model.CreateSource(&auth_model.Source{
return createSource(ctx, &auth_model.Source{
Type: auth_model.OAuth2,
Name: c.String("name"),
IsActive: true,
Expand All @@ -508,6 +510,7 @@ func runUpdateOauth(c *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}
audit.Init()

source, err := auth_model.GetSourceByID(c.Int64("id"))
if err != nil {
Expand Down Expand Up @@ -600,7 +603,7 @@ func runUpdateOauth(c *cli.Context) error {
oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config

return auth_model.UpdateSource(source)
return updateSource(ctx, source)
}

func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
Expand Down Expand Up @@ -646,6 +649,7 @@ func runAddSMTP(c *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}
audit.Init()

if !c.IsSet("name") || len(c.String("name")) == 0 {
return errors.New("name must be set")
Expand All @@ -671,7 +675,7 @@ func runAddSMTP(c *cli.Context) error {
smtpConfig.Auth = "PLAIN"
}

return auth_model.CreateSource(&auth_model.Source{
return createSource(ctx, &auth_model.Source{
Type: auth_model.SMTP,
Name: c.String("name"),
IsActive: active,
Expand All @@ -690,6 +694,7 @@ func runUpdateSMTP(c *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}
audit.Init()

source, err := auth_model.GetSourceByID(c.Int64("id"))
if err != nil {
Expand All @@ -712,7 +717,27 @@ func runUpdateSMTP(c *cli.Context) error {

source.Cfg = smtpConfig

return auth_model.UpdateSource(source)
return updateSource(ctx, source)
}

func createSource(ctx context.Context, source *auth_model.Source) error {
if err := auth_model.CreateSource(source); err != nil {
return err
}

audit.Record(audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String())
6543 marked this conversation as resolved.
Show resolved Hide resolved

return nil
}

func updateSource(ctx context.Context, source *auth_model.Source) error {
if err := auth_model.UpdateSource(source); err != nil {
return err
}

audit.Record(audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, source, "Updated authentication source %s.", source.Name)

return nil
}

func runListAuth(c *cli.Context) error {
Expand Down Expand Up @@ -760,11 +785,12 @@ func runDeleteAuth(c *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}
audit.Init()

source, err := auth_model.GetSourceByID(c.Int64("id"))
if err != nil {
return err
}

return auth_service.DeleteSource(source)
return auth_service.DeleteSource(audit.NewCLIUser(), source)
}
82 changes: 32 additions & 50 deletions cmd/admin_auth_ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/services/audit"
"code.gitea.io/gitea/services/auth/source/ldap"

"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -308,58 +309,26 @@ func (a *authService) getAuthSource(c *cli.Context, authType auth.Type) (*auth.S

// addLdapBindDn adds a new LDAP via Bind DN authentication source.
func (a *authService) addLdapBindDn(c *cli.Context) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
return err
}

ctx, cancel := installSignals()
defer cancel()

if err := a.initDB(ctx); err != nil {
return err
}

authSource := &auth.Source{
Type: auth.LDAP,
IsActive: true, // active by default
Cfg: &ldap.Source{
Enabled: true, // always true
},
}

parseAuthSource(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}

return a.createAuthSource(authSource)
return a.addLdapSource(c, auth.LDAP, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute")
}

// updateLdapBindDn updates a new LDAP via Bind DN authentication source.
func (a *authService) updateLdapBindDn(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()

if err := a.initDB(ctx); err != nil {
return err
}

authSource, err := a.getAuthSource(c, auth.LDAP)
if err != nil {
return err
}

parseAuthSource(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}

return a.updateAuthSource(authSource)
return a.updateLdapSource(c, auth.LDAP)
}

// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
return a.addLdapSource(c, auth.DLDAP, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute")
}

// updateLdapBindDn updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
return a.updateLdapSource(c, auth.DLDAP)
}

func (a *authService) addLdapSource(c *cli.Context, authType auth.Type, args ...string) error {
if err := argsSet(c, args...); err != nil {
return err
}

Expand All @@ -369,9 +338,10 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
if err := a.initDB(ctx); err != nil {
return err
}
audit.Init()

authSource := &auth.Source{
Type: auth.DLDAP,
Type: authType,
IsActive: true, // active by default
Cfg: &ldap.Source{
Enabled: true, // always true
Expand All @@ -383,19 +353,25 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
return err
}

return a.createAuthSource(authSource)
if err := a.createAuthSource(authSource); err != nil {
return err
}

audit.Record(audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, authSource, "Created authentication source %s [%s].", authSource.Name, authSource.Type.String())

return nil
}

// updateLdapBindDn updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
func (a *authService) updateLdapSource(c *cli.Context, authType auth.Type) error {
ctx, cancel := installSignals()
defer cancel()

if err := a.initDB(ctx); err != nil {
return err
}
audit.Init()

authSource, err := a.getAuthSource(c, auth.DLDAP)
authSource, err := a.getAuthSource(c, authType)
if err != nil {
return err
}
Expand All @@ -405,5 +381,11 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
return err
}

return a.updateAuthSource(authSource)
if err := a.updateAuthSource(authSource); err != nil {
return err
}

audit.Record(audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, authSource, "Updated authentication source %s.", authSource.Name)

return nil
}
6 changes: 6 additions & 0 deletions cmd/admin_user_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/audit"

"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -95,6 +96,7 @@ func runCreateUser(c *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}
audit.Init()

var password string
if c.IsSet("password") {
Expand Down Expand Up @@ -150,6 +152,8 @@ func runCreateUser(c *cli.Context) error {
return fmt.Errorf("CreateUser: %w", err)
}

audit.Record(audit.UserCreate, audit.NewCLIUser(), u, u, "Created user %s.", u.Name)

if c.Bool("access-token") {
t := &auth_model.AccessToken{
Name: "gitea-admin",
Expand All @@ -160,6 +164,8 @@ func runCreateUser(c *cli.Context) error {
return err
}

audit.Record(audit.UserAccessTokenAdd, audit.NewCLIUser(), u, t, "Added access token %s for user %s with scope %s.", t.Name, u.Name, t.Scope)

fmt.Printf("Access token was successfully created... %s\n", t.Token)
}

Expand Down
5 changes: 3 additions & 2 deletions cmd/admin_user_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/services/audit"
user_service "code.gitea.io/gitea/services/user"

"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -51,7 +52,7 @@ func runDeleteUser(c *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}

audit.Init()
delvh marked this conversation as resolved.
Show resolved Hide resolved
if err := storage.Init(); err != nil {
return err
}
Expand All @@ -76,5 +77,5 @@ func runDeleteUser(c *cli.Context) error {
return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
}

return user_service.DeleteUser(ctx, user, c.Bool("purge"))
return user_service.DeleteUser(ctx, audit.NewCLIUser(), user, c.Bool("purge"))
}
4 changes: 4 additions & 0 deletions cmd/admin_user_generate_access_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/services/audit"

"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -51,6 +52,7 @@ func runGenerateAccessToken(c *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}
audit.Init()

user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
Expand Down Expand Up @@ -83,6 +85,8 @@ func runGenerateAccessToken(c *cli.Context) error {
return err
}

audit.Record(audit.UserAccessTokenAdd, audit.NewCLIUser(), user, t, "Added access token %s for user %s with scope %s.", t.Name, user.Name, t.Scope)

if c.Bool("raw") {
fmt.Printf("%s\n", t.Token)
} else {
Expand Down
32 changes: 32 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,38 @@ LEVEL = Info
;; Host address
;ADDR =

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[audit]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Enable logging of audit events
;ENABLED = false
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
;; Comma seperated list of appenders to log to (possible values: log, file)
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
;APPENDER =
silverwind marked this conversation as resolved.
Show resolved Hide resolved
6543 marked this conversation as resolved.
Show resolved Hide resolved
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Creating specific log configuration
6543 marked this conversation as resolved.
Show resolved Hide resolved
;;
;; You can set specific configuration for individual appenders. Currently only "file" uses additional configuration.
;;
;[audit.file]
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

By the way, I am unsure if we should really hardcode the rotation into Gitea.
It is not Gitea's job to make sure the audit log is cleaned up.
Instead, we should instruct people how they can configure their system to do it.
I don't think there's any system where this isn't possible, in the worst case using an extra library.
Nevertheless, that way, they can manage their configuration in the one central place intended for it instead of needing to dig through the Gitea docs.

;; Set the file name for the logger. If this is a relative path this
;; will be relative to log.ROOT_PATH
;; Defaults to log.ROOT_PATH/audit.log
;FILENAME =
;; This enables automated audit log rotate, default is true
;ROTATE = true
;; Maximum file size in bytes before rotating takes place (format `1000`, `1 MB`, `1 GiB`)
;MAXIMUM_SIZE = 256 MB
;; Rotate audit log daily, default is true
;ROTATE_DAILY = true
;; Delete the audit log file after n days, default is 7
;KEEP_DAYS = 7
;; Compress audit logs with gzip
;COMPRESS = true
;; Compression level see godoc for compress/gzip
;COMPRESSION_LEVEL = -1

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[git]
Expand Down
Loading