tangled
alpha
login
or
join now
whitequark.org
/
git-pages
10
fork
atom
[mirror] Scalable static site server for Git forges (like GitHub Pages)
10
fork
atom
overview
issues
pulls
pipelines
Record the authorized forge user's name in the audit log.
miyuko
1 week ago
9e966401
3e377986
+184
-24
5 changed files
expand all
collapse all
unified
split
src
audit.go
auth.go
pages.go
schema.pb.go
schema.proto
+6
src/audit.go
···
102
102
if record.Principal.GetIpAddress() != "" {
103
103
items = append(items, record.Principal.GetIpAddress())
104
104
}
105
105
+
if record.Principal.GetForgeUser() != nil {
106
106
+
items = append(items, fmt.Sprintf("%s/%s(%d)",
107
107
+
record.Principal.GetForgeUser().GetOrigin(),
108
108
+
record.Principal.GetForgeUser().GetHandle(),
109
109
+
record.Principal.GetForgeUser().GetId()))
110
110
+
}
105
111
if record.Principal.GetCliAdmin() {
106
112
items = append(items, "<cli-admin>")
107
113
}
+71
-5
src/auth.go
···
106
106
repoURLs []string
107
107
// Only the exact branch is allowed.
108
108
branch string
109
109
+
// The authorized forge user.
110
110
+
forgeUser *ForgeUser
109
111
}
110
112
111
113
func authorizeDNSChallenge(r *http.Request) (*Authorization, error) {
···
266
268
267
269
if userName, found := pattern.Matches(host); found {
268
270
repoURL, branch := pattern.ApplyTemplate(userName, projectName)
269
269
-
return &Authorization{[]string{repoURL}, branch}, nil
271
271
+
return &Authorization{repoURLs: []string{repoURL}, branch: branch}, nil
270
272
} else {
271
273
return nil, AuthError{
272
274
http.StatusUnauthorized,
···
606
608
return nil
607
609
}
608
610
611
611
+
// Gogs, Gitea, and Forgejo all support the same API here.
612
612
+
func fetchGogsAuthorizedUser(baseURL *url.URL, authorization string) (*ForgeUser, error) {
613
613
+
request, err := http.NewRequest("GET", baseURL.JoinPath("/api/v1/user").String(), nil)
614
614
+
if err != nil {
615
615
+
panic(err) // misconfiguration
616
616
+
}
617
617
+
request.Header.Set("Accept", "application/json")
618
618
+
request.Header.Set("Authorization", authorization)
619
619
+
620
620
+
httpClient := http.Client{Timeout: 5 * time.Second}
621
621
+
response, err := httpClient.Do(request)
622
622
+
if err != nil {
623
623
+
return nil, AuthError{
624
624
+
http.StatusServiceUnavailable,
625
625
+
fmt.Sprintf("cannot fetch authorized forge user: %s", err),
626
626
+
}
627
627
+
}
628
628
+
defer response.Body.Close()
629
629
+
630
630
+
if response.StatusCode != http.StatusOK {
631
631
+
return nil, AuthError{
632
632
+
http.StatusServiceUnavailable,
633
633
+
fmt.Sprintf(
634
634
+
"cannot fetch authorized forge user: GET %s returned %s",
635
635
+
request.URL,
636
636
+
response.Status,
637
637
+
),
638
638
+
}
639
639
+
}
640
640
+
decoder := json.NewDecoder(response.Body)
641
641
+
642
642
+
var userInfo struct {
643
643
+
ID int64
644
644
+
Login string
645
645
+
}
646
646
+
if err = decoder.Decode(&userInfo); err != nil {
647
647
+
return nil, errors.Join(AuthError{
648
648
+
http.StatusServiceUnavailable,
649
649
+
fmt.Sprintf(
650
650
+
"cannot fetch authorized forge user: GET %s returned malformed JSON",
651
651
+
request.URL,
652
652
+
),
653
653
+
}, err)
654
654
+
}
655
655
+
656
656
+
origin := request.URL.Hostname()
657
657
+
return &ForgeUser{
658
658
+
Origin: &origin,
659
659
+
Id: &userInfo.ID,
660
660
+
Handle: &userInfo.Login,
661
661
+
}, nil
662
662
+
}
663
663
+
609
664
func authorizeForgeWithToken(r *http.Request) (*Authorization, error) {
610
665
authorization := r.Header.Get("Forge-Authorization")
611
666
if authorization == "" {
···
640
695
continue
641
696
}
642
697
643
643
-
// This will actually be ignored by the callers of AuthorizeUpdateFromArchive and
644
644
-
// AuthorizeDeletion, but we return this information as it makes sense to do
645
645
-
// contextually here.
646
646
-
return &Authorization{[]string{repoURL}, branch}, nil
698
698
+
authorizedUser, err := fetchGogsAuthorizedUser(parsedRepoURL, authorization)
699
699
+
if err != nil {
700
700
+
errs = append(errs, err)
701
701
+
continue
702
702
+
}
703
703
+
704
704
+
return &Authorization{
705
705
+
// This will actually be ignored by the callers of AuthorizeUpdateFromArchive and
706
706
+
// AuthorizeDeletion, but we return this information as it makes sense to do
707
707
+
// contextually here.
708
708
+
repoURLs: []string{repoURL},
709
709
+
branch: branch,
710
710
+
711
711
+
forgeUser: authorizedUser,
712
712
+
}, nil
647
713
}
648
714
}
649
715
+9
-5
src/pages.go
···
499
499
result = UpdateFromRepository(ctx, webRoot, repoURL, branch)
500
500
501
501
default:
502
502
-
_, err := AuthorizeUpdateFromArchive(r)
503
503
-
if err != nil {
502
502
+
if auth, err := AuthorizeUpdateFromArchive(r); err != nil {
504
503
return err
504
504
+
} else if auth.forgeUser != nil {
505
505
+
GetPrincipal(r.Context()).ForgeUser = auth.forgeUser
505
506
}
506
507
507
508
if checkDryRun(w, r) {
···
531
532
return err
532
533
}
533
534
534
534
-
if _, err = AuthorizeUpdateFromArchive(r); err != nil {
535
535
+
if auth, err := AuthorizeUpdateFromArchive(r); err != nil {
535
536
return err
537
537
+
} else if auth.forgeUser != nil {
538
538
+
GetPrincipal(r.Context()).ForgeUser = auth.forgeUser
536
539
}
537
540
538
541
if checkDryRun(w, r) {
···
661
664
return err
662
665
}
663
666
664
664
-
_, err = AuthorizeDeletion(r)
665
665
-
if err != nil {
667
667
+
if auth, err := AuthorizeDeletion(r); err != nil {
666
668
return err
669
669
+
} else if auth.forgeUser != nil {
670
670
+
GetPrincipal(r.Context()).ForgeUser = auth.forgeUser
667
671
}
668
672
669
673
if checkDryRun(w, r) {
+91
-14
src/schema.pb.go
···
750
750
state protoimpl.MessageState `protogen:"open.v1"`
751
751
IpAddress *string `protobuf:"bytes,1,opt,name=ip_address,json=ipAddress" json:"ip_address,omitempty"`
752
752
CliAdmin *bool `protobuf:"varint,2,opt,name=cli_admin,json=cliAdmin" json:"cli_admin,omitempty"`
753
753
+
ForgeUser *ForgeUser `protobuf:"bytes,3,opt,name=forge_user,json=forgeUser" json:"forge_user,omitempty"`
753
754
unknownFields protoimpl.UnknownFields
754
755
sizeCache protoimpl.SizeCache
755
756
}
···
798
799
return false
799
800
}
800
801
802
802
+
func (x *Principal) GetForgeUser() *ForgeUser {
803
803
+
if x != nil {
804
804
+
return x.ForgeUser
805
805
+
}
806
806
+
return nil
807
807
+
}
808
808
+
809
809
+
type ForgeUser struct {
810
810
+
state protoimpl.MessageState `protogen:"open.v1"`
811
811
+
Origin *string `protobuf:"bytes,1,opt,name=origin" json:"origin,omitempty"`
812
812
+
Id *int64 `protobuf:"varint,2,opt,name=id" json:"id,omitempty"`
813
813
+
Handle *string `protobuf:"bytes,3,opt,name=handle" json:"handle,omitempty"`
814
814
+
unknownFields protoimpl.UnknownFields
815
815
+
sizeCache protoimpl.SizeCache
816
816
+
}
817
817
+
818
818
+
func (x *ForgeUser) Reset() {
819
819
+
*x = ForgeUser{}
820
820
+
mi := &file_schema_proto_msgTypes[8]
821
821
+
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
822
822
+
ms.StoreMessageInfo(mi)
823
823
+
}
824
824
+
825
825
+
func (x *ForgeUser) String() string {
826
826
+
return protoimpl.X.MessageStringOf(x)
827
827
+
}
828
828
+
829
829
+
func (*ForgeUser) ProtoMessage() {}
830
830
+
831
831
+
func (x *ForgeUser) ProtoReflect() protoreflect.Message {
832
832
+
mi := &file_schema_proto_msgTypes[8]
833
833
+
if x != nil {
834
834
+
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
835
835
+
if ms.LoadMessageInfo() == nil {
836
836
+
ms.StoreMessageInfo(mi)
837
837
+
}
838
838
+
return ms
839
839
+
}
840
840
+
return mi.MessageOf(x)
841
841
+
}
842
842
+
843
843
+
// Deprecated: Use ForgeUser.ProtoReflect.Descriptor instead.
844
844
+
func (*ForgeUser) Descriptor() ([]byte, []int) {
845
845
+
return file_schema_proto_rawDescGZIP(), []int{8}
846
846
+
}
847
847
+
848
848
+
func (x *ForgeUser) GetOrigin() string {
849
849
+
if x != nil && x.Origin != nil {
850
850
+
return *x.Origin
851
851
+
}
852
852
+
return ""
853
853
+
}
854
854
+
855
855
+
func (x *ForgeUser) GetId() int64 {
856
856
+
if x != nil && x.Id != nil {
857
857
+
return *x.Id
858
858
+
}
859
859
+
return 0
860
860
+
}
861
861
+
862
862
+
func (x *ForgeUser) GetHandle() string {
863
863
+
if x != nil && x.Handle != nil {
864
864
+
return *x.Handle
865
865
+
}
866
866
+
return ""
867
867
+
}
868
868
+
801
869
var File_schema_proto protoreflect.FileDescriptor
802
870
803
871
const file_schema_proto_rawDesc = "" +
···
853
921
"\x06domain\x18\n" +
854
922
" \x01(\tR\x06domain\x12\x18\n" +
855
923
"\aproject\x18\v \x01(\tR\aproject\x12%\n" +
856
856
-
"\bmanifest\x18\f \x01(\v2\t.ManifestR\bmanifest\"G\n" +
924
924
+
"\bmanifest\x18\f \x01(\v2\t.ManifestR\bmanifest\"r\n" +
857
925
"\tPrincipal\x12\x1d\n" +
858
926
"\n" +
859
927
"ip_address\x18\x01 \x01(\tR\tipAddress\x12\x1b\n" +
860
860
-
"\tcli_admin\x18\x02 \x01(\bR\bcliAdmin*V\n" +
928
928
+
"\tcli_admin\x18\x02 \x01(\bR\bcliAdmin\x12)\n" +
929
929
+
"\n" +
930
930
+
"forge_user\x18\x03 \x01(\v2\n" +
931
931
+
".ForgeUserR\tforgeUser\"K\n" +
932
932
+
"\tForgeUser\x12\x16\n" +
933
933
+
"\x06origin\x18\x01 \x01(\tR\x06origin\x12\x0e\n" +
934
934
+
"\x02id\x18\x02 \x01(\x03R\x02id\x12\x16\n" +
935
935
+
"\x06handle\x18\x03 \x01(\tR\x06handle*V\n" +
861
936
"\x04Type\x12\x10\n" +
862
937
"\fInvalidEntry\x10\x00\x12\r\n" +
863
938
"\tDirectory\x10\x01\x12\x0e\n" +
···
889
964
}
890
965
891
966
var file_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
892
892
-
var file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
967
967
+
var file_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
893
968
var file_schema_proto_goTypes = []any{
894
969
(Type)(0), // 0: Type
895
970
(Transform)(0), // 1: Transform
···
902
977
(*Manifest)(nil), // 8: Manifest
903
978
(*AuditRecord)(nil), // 9: AuditRecord
904
979
(*Principal)(nil), // 10: Principal
905
905
-
nil, // 11: Manifest.ContentsEntry
906
906
-
(*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
980
980
+
(*ForgeUser)(nil), // 11: ForgeUser
981
981
+
nil, // 12: Manifest.ContentsEntry
982
982
+
(*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp
907
983
}
908
984
var file_schema_proto_depIdxs = []int32{
909
985
0, // 0: Entry.type:type_name -> Type
910
986
1, // 1: Entry.transform:type_name -> Transform
911
987
5, // 2: HeaderRule.header_map:type_name -> Header
912
912
-
11, // 3: Manifest.contents:type_name -> Manifest.ContentsEntry
988
988
+
12, // 3: Manifest.contents:type_name -> Manifest.ContentsEntry
913
989
4, // 4: Manifest.redirects:type_name -> RedirectRule
914
990
6, // 5: Manifest.headers:type_name -> HeaderRule
915
991
7, // 6: Manifest.problems:type_name -> Problem
916
916
-
12, // 7: AuditRecord.timestamp:type_name -> google.protobuf.Timestamp
992
992
+
13, // 7: AuditRecord.timestamp:type_name -> google.protobuf.Timestamp
917
993
2, // 8: AuditRecord.event:type_name -> AuditEvent
918
994
10, // 9: AuditRecord.principal:type_name -> Principal
919
995
8, // 10: AuditRecord.manifest:type_name -> Manifest
920
920
-
3, // 11: Manifest.ContentsEntry.value:type_name -> Entry
921
921
-
12, // [12:12] is the sub-list for method output_type
922
922
-
12, // [12:12] is the sub-list for method input_type
923
923
-
12, // [12:12] is the sub-list for extension type_name
924
924
-
12, // [12:12] is the sub-list for extension extendee
925
925
-
0, // [0:12] is the sub-list for field type_name
996
996
+
11, // 11: Principal.forge_user:type_name -> ForgeUser
997
997
+
3, // 12: Manifest.ContentsEntry.value:type_name -> Entry
998
998
+
13, // [13:13] is the sub-list for method output_type
999
999
+
13, // [13:13] is the sub-list for method input_type
1000
1000
+
13, // [13:13] is the sub-list for extension type_name
1001
1001
+
13, // [13:13] is the sub-list for extension extendee
1002
1002
+
0, // [0:13] is the sub-list for field type_name
926
1003
}
927
1004
928
1005
func init() { file_schema_proto_init() }
···
936
1013
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
937
1014
RawDescriptor: unsafe.Slice(unsafe.StringData(file_schema_proto_rawDesc), len(file_schema_proto_rawDesc)),
938
1015
NumEnums: 3,
939
939
-
NumMessages: 9,
1016
1016
+
NumMessages: 10,
940
1017
NumExtensions: 0,
941
1018
NumServices: 0,
942
1019
},
+7
src/schema.proto
···
132
132
message Principal {
133
133
string ip_address = 1;
134
134
bool cli_admin = 2;
135
135
+
ForgeUser forge_user = 3;
136
136
+
}
137
137
+
138
138
+
message ForgeUser {
139
139
+
string origin = 1;
140
140
+
int64 id = 2;
141
141
+
string handle = 3;
135
142
}