mirror of
https://codeberg.org/git-pages/git-pages.git
synced 2026-08-17 09:26:04 +00:00
Optionally include principal's IP address in audit record metadata.
Currently this doesn't handle `X-Forwarded-For` and as such isn't very useful. It is surprisingly difficult to find a high-quality library for parsing `X-Forwarded-For` and a solution will have to be found.
This commit is contained in:
@@ -57,6 +57,7 @@ allowed-custom-headers = ["X-Clacks-Overhead"]
|
||||
[audit]
|
||||
node-id = 0
|
||||
collect = false
|
||||
include-ip = false
|
||||
notify-url = ""
|
||||
|
||||
[observability]
|
||||
|
||||
@@ -28,6 +28,22 @@ var (
|
||||
})
|
||||
)
|
||||
|
||||
type principalKey struct{}
|
||||
|
||||
var PrincipalKey = principalKey{}
|
||||
|
||||
func WithPrincipal(ctx context.Context) context.Context {
|
||||
principal := &Principal{}
|
||||
return context.WithValue(ctx, PrincipalKey, principal)
|
||||
}
|
||||
|
||||
func GetPrincipal(ctx context.Context) *Principal {
|
||||
if principal, ok := ctx.Value(PrincipalKey).(*Principal); ok {
|
||||
return principal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AuditID int64
|
||||
|
||||
func GenerateAuditID() AuditID {
|
||||
@@ -95,6 +111,7 @@ func (audited *auditedBackend) appendNewAuditRecord(ctx context.Context, record
|
||||
id := GenerateAuditID()
|
||||
record.Id = proto.Int64(int64(id))
|
||||
record.Timestamp = timestamppb.Now()
|
||||
record.Principal = GetPrincipal(ctx)
|
||||
|
||||
err = audited.Backend.AppendAuditLog(ctx, id, record)
|
||||
if err != nil {
|
||||
|
||||
@@ -152,6 +152,8 @@ type AuditConfig struct {
|
||||
NodeID int `toml:"node-id"`
|
||||
// Whether audit reports should be stored whenever an audit event occurs.
|
||||
Collect bool `toml:"collect"`
|
||||
// Whether audit reports should include principal's IP address.
|
||||
IncludeIPs bool `toml:"include-ip"`
|
||||
// Endpoint to notify with a `GET /<notify-url>?<id>` whenever an audit event occurs.
|
||||
NotifyURL *URL `toml:"notify-url"`
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"github.com/pquerna/cachecontrol/cacheobject"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const notFoundPage = "404.html"
|
||||
@@ -783,6 +785,12 @@ func postPage(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
|
||||
func ServePages(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(WithPrincipal(r.Context()))
|
||||
if config.Audit.IncludeIPs {
|
||||
if ipAddress, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||
GetPrincipal(r.Context()).IpAddress = proto.String(ipAddress)
|
||||
}
|
||||
}
|
||||
// We want upstream health checks to be done as closely to the normal flow as possible;
|
||||
// any intentional deviation is an opportunity to miss an issue that will affect our
|
||||
// visitors but not our health checks.
|
||||
|
||||
+75
-16
@@ -657,6 +657,7 @@ type AuditRecord struct {
|
||||
Id *int64 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
|
||||
Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp" json:"timestamp,omitempty"`
|
||||
Event *AuditEvent `protobuf:"varint,3,opt,name=event,enum=AuditEvent" json:"event,omitempty"`
|
||||
Principal *Principal `protobuf:"bytes,4,opt,name=principal" json:"principal,omitempty"`
|
||||
// Affected resource.
|
||||
Domain *string `protobuf:"bytes,10,opt,name=domain" json:"domain,omitempty"`
|
||||
Project *string `protobuf:"bytes,11,opt,name=project" json:"project,omitempty"` // only for `*Manifest` events
|
||||
@@ -717,6 +718,13 @@ func (x *AuditRecord) GetEvent() AuditEvent {
|
||||
return AuditEvent_InvalidEvent
|
||||
}
|
||||
|
||||
func (x *AuditRecord) GetPrincipal() *Principal {
|
||||
if x != nil {
|
||||
return x.Principal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AuditRecord) GetDomain() string {
|
||||
if x != nil && x.Domain != nil {
|
||||
return *x.Domain
|
||||
@@ -738,6 +746,50 @@ func (x *AuditRecord) GetManifest() *Manifest {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Principal struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
IpAddress *string `protobuf:"bytes,1,opt,name=ip_address,json=ipAddress" json:"ip_address,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Principal) Reset() {
|
||||
*x = Principal{}
|
||||
mi := &file_schema_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Principal) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Principal) ProtoMessage() {}
|
||||
|
||||
func (x *Principal) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_schema_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Principal.ProtoReflect.Descriptor instead.
|
||||
func (*Principal) Descriptor() ([]byte, []int) {
|
||||
return file_schema_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *Principal) GetIpAddress() string {
|
||||
if x != nil && x.IpAddress != nil {
|
||||
return *x.IpAddress
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_schema_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_schema_proto_rawDesc = "" +
|
||||
@@ -783,15 +835,20 @@ const file_schema_proto_rawDesc = "" +
|
||||
"\bproblems\x18\a \x03(\v2\b.ProblemR\bproblems\x1aC\n" +
|
||||
"\rContentsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x1c\n" +
|
||||
"\x05value\x18\x02 \x01(\v2\x06.EntryR\x05value:\x028\x01\"\xd3\x01\n" +
|
||||
"\x05value\x18\x02 \x01(\v2\x06.EntryR\x05value:\x028\x01\"\xfd\x01\n" +
|
||||
"\vAuditRecord\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x03R\x02id\x128\n" +
|
||||
"\ttimestamp\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12!\n" +
|
||||
"\x05event\x18\x03 \x01(\x0e2\v.AuditEventR\x05event\x12\x16\n" +
|
||||
"\x05event\x18\x03 \x01(\x0e2\v.AuditEventR\x05event\x12(\n" +
|
||||
"\tprincipal\x18\x04 \x01(\v2\n" +
|
||||
".PrincipalR\tprincipal\x12\x16\n" +
|
||||
"\x06domain\x18\n" +
|
||||
" \x01(\tR\x06domain\x12\x18\n" +
|
||||
"\aproject\x18\v \x01(\tR\aproject\x12%\n" +
|
||||
"\bmanifest\x18\f \x01(\v2\t.ManifestR\bmanifest*V\n" +
|
||||
"\bmanifest\x18\f \x01(\v2\t.ManifestR\bmanifest\"*\n" +
|
||||
"\tPrincipal\x12\x1d\n" +
|
||||
"\n" +
|
||||
"ip_address\x18\x01 \x01(\tR\tipAddress*V\n" +
|
||||
"\x04Type\x12\x10\n" +
|
||||
"\fInvalidEntry\x10\x00\x12\r\n" +
|
||||
"\tDirectory\x10\x01\x12\x0e\n" +
|
||||
@@ -823,7 +880,7 @@ func file_schema_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||
var file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||
var file_schema_proto_goTypes = []any{
|
||||
(Type)(0), // 0: Type
|
||||
(Transform)(0), // 1: Transform
|
||||
@@ -835,26 +892,28 @@ var file_schema_proto_goTypes = []any{
|
||||
(*Problem)(nil), // 7: Problem
|
||||
(*Manifest)(nil), // 8: Manifest
|
||||
(*AuditRecord)(nil), // 9: AuditRecord
|
||||
nil, // 10: Manifest.ContentsEntry
|
||||
(*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
|
||||
(*Principal)(nil), // 10: Principal
|
||||
nil, // 11: Manifest.ContentsEntry
|
||||
(*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
|
||||
}
|
||||
var file_schema_proto_depIdxs = []int32{
|
||||
0, // 0: Entry.type:type_name -> Type
|
||||
1, // 1: Entry.transform:type_name -> Transform
|
||||
5, // 2: HeaderRule.header_map:type_name -> Header
|
||||
10, // 3: Manifest.contents:type_name -> Manifest.ContentsEntry
|
||||
11, // 3: Manifest.contents:type_name -> Manifest.ContentsEntry
|
||||
4, // 4: Manifest.redirects:type_name -> RedirectRule
|
||||
6, // 5: Manifest.headers:type_name -> HeaderRule
|
||||
7, // 6: Manifest.problems:type_name -> Problem
|
||||
11, // 7: AuditRecord.timestamp:type_name -> google.protobuf.Timestamp
|
||||
12, // 7: AuditRecord.timestamp:type_name -> google.protobuf.Timestamp
|
||||
2, // 8: AuditRecord.event:type_name -> AuditEvent
|
||||
8, // 9: AuditRecord.manifest:type_name -> Manifest
|
||||
3, // 10: Manifest.ContentsEntry.value:type_name -> Entry
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
10, // 9: AuditRecord.principal:type_name -> Principal
|
||||
8, // 10: AuditRecord.manifest:type_name -> Manifest
|
||||
3, // 11: Manifest.ContentsEntry.value:type_name -> Entry
|
||||
12, // [12:12] is the sub-list for method output_type
|
||||
12, // [12:12] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_schema_proto_init() }
|
||||
@@ -868,7 +927,7 @@ func file_schema_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_schema_proto_rawDesc), len(file_schema_proto_rawDesc)),
|
||||
NumEnums: 3,
|
||||
NumMessages: 8,
|
||||
NumMessages: 9,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -119,6 +119,7 @@ message AuditRecord {
|
||||
int64 id = 1;
|
||||
google.protobuf.Timestamp timestamp = 2;
|
||||
AuditEvent event = 3;
|
||||
Principal principal = 4;
|
||||
|
||||
// Affected resource.
|
||||
string domain = 10;
|
||||
@@ -127,3 +128,7 @@ message AuditRecord {
|
||||
// Snapshot of site manifest.
|
||||
Manifest manifest = 12; // only for `*Manifest` events
|
||||
}
|
||||
|
||||
message Principal {
|
||||
string ip_address = 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user