diff --git a/go.mod b/go.mod index c687267..3e840fc 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/prometheus/client_golang v1.13.0 github.com/rodaine/table v1.0.1 github.com/sony/sonyflake v1.1.0 + github.com/stretchr/testify v1.8.0 github.com/xhit/go-str2duration/v2 v2.0.0 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b @@ -44,6 +45,7 @@ require ( github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/glebarez/go-sqlite v1.18.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect @@ -75,6 +77,7 @@ require ( github.com/mholt/acmez v1.0.4 // indirect github.com/miekg/dns v1.1.50 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect diff --git a/internal/domain/acl.go b/internal/domain/acl.go index 3fc35db..bb08153 100644 --- a/internal/domain/acl.go +++ b/internal/domain/acl.go @@ -4,6 +4,7 @@ import ( "database/sql/driver" "encoding/json" "fmt" + "github.com/hashicorp/go-multierror" "gorm.io/gorm" "gorm.io/gorm/schema" "net/netip" @@ -13,9 +14,10 @@ import ( ) type ACLPolicy struct { - Groups map[string][]string `json:"groups,omitempty"` - Hosts map[string]string `json:"hosts,omitempty"` - ACLs []ACL `json:"acls"` + Groups map[string][]string `json:"groups,omitempty"` + Hosts map[string]string `json:"hosts,omitempty"` + ACLs []ACL `json:"acls"` + TagOwners map[string][]string `json:"tag_owners"` } type ACL struct { @@ -24,34 +26,6 @@ type ACL struct { Dst []string `json:"dst"` } -func (i *ACLPolicy) Scan(destination interface{}) error { - switch value := destination.(type) { - case []byte: - return json.Unmarshal(value, i) - default: - return fmt.Errorf("unexpected data type %T", destination) - } -} - -func (i ACLPolicy) Value() (driver.Value, error) { - bytes, err := json.Marshal(i) - return bytes, err -} - -// GormDataType gorm common data type -func (ACLPolicy) GormDataType() string { - return "json" -} - -// GormDBDataType gorm db data type -func (ACLPolicy) GormDBDataType(db *gorm.DB, field *schema.Field) string { - switch db.Dialector.Name() { - case "sqlite": - return "JSON" - } - return "" -} - func DefaultPolicy() ACLPolicy { return ACLPolicy{ ACLs: []ACL{ @@ -64,6 +38,55 @@ func DefaultPolicy() ACLPolicy { } } +func (a ACLPolicy) CheckTags(tags []string) error { + var result *multierror.Error + for _, t := range tags { + if _, ok := a.TagOwners[t]; !ok { + result = multierror.Append(result, fmt.Errorf("tag [%s] is invalid or not permitted", t)) + } + } + return result.ErrorOrNil() +} + +func (a ACLPolicy) CheckTagOwners(tags []string, p *User) error { + var result *multierror.Error + for _, t := range tags { + if ok := a.IsTagOwner(t, p); !ok { + result = multierror.Append(result, fmt.Errorf("tag [%s] is invalid or not permitted", t)) + } + } + return result.ErrorOrNil() +} + +func (a ACLPolicy) IsTagOwner(tag string, p *User) bool { + if tagOwners, ok := a.TagOwners[tag]; ok { + if p.UserType == UserTypeService { + return true + } + return a.validateTagOwners(tagOwners, p) + } + return false +} + +func (a ACLPolicy) validateTagOwners(tagOwners []string, p *User) bool { + for _, alias := range tagOwners { + if strings.HasPrefix(alias, "group:") { + if group, ok := a.Groups[alias]; ok { + for _, groupMember := range group { + if groupMember == p.Name { + return true + } + } + } + } else { + if alias == p.Name { + return true + } + } + } + return false +} + func (a ACLPolicy) IsValidPeer(src *Machine, dest *Machine) bool { if !src.HasTags() && !dest.HasTags() && dest.HasUser(src.User.Name) { return true @@ -260,6 +283,34 @@ func (a ACLPolicy) expandValuePortToPortRange(s string) ([]tailcfg.PortRange, er return ports, nil } +func (i *ACLPolicy) Scan(destination interface{}) error { + switch value := destination.(type) { + case []byte: + return json.Unmarshal(value, i) + default: + return fmt.Errorf("unexpected data type %T", destination) + } +} + +func (i ACLPolicy) Value() (driver.Value, error) { + bytes, err := json.Marshal(i) + return bytes, err +} + +// GormDataType gorm common data type +func (ACLPolicy) GormDataType() string { + return "json" +} + +// GormDBDataType gorm db data type +func (ACLPolicy) GormDBDataType(db *gorm.DB, field *schema.Field) string { + switch db.Dialector.Name() { + case "sqlite": + return "JSON" + } + return "" +} + type StringSet struct { items map[string]bool } diff --git a/internal/domain/acl_test.go b/internal/domain/acl_test.go new file mode 100644 index 0000000..d93bb9e --- /dev/null +++ b/internal/domain/acl_test.go @@ -0,0 +1,71 @@ +package domain + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestACLPolicy_IsTagOwner(t *testing.T) { + policy := ACLPolicy{ + Groups: map[string][]string{ + "group:engineers": {"jane@example.com"}, + }, + TagOwners: map[string][]string{ + "tag:web": {"john@example.com", "group:engineers"}, + }} + + testCases := []struct { + name string + tag string + userName string + userType UserType + expectErr bool + }{ + { + name: "system admin is always a valid owner", + tag: "tag:web", + userName: "system admin", + userType: UserTypeService, + expectErr: false, + }, + { + name: "direct tag owner", + tag: "tag:web", + userName: "john@example.com", + userType: UserTypePerson, + expectErr: false, + }, + { + name: "owner by group", + tag: "tag:web", + userName: "jane@example.com", + userType: UserTypePerson, + expectErr: false, + }, + { + name: "unknown owner", + tag: "tag:web", + userName: "nick@example.com", + userType: UserTypePerson, + expectErr: true, + }, + { + name: "unknown tag", + tag: "tag:unknown", + userName: "jane@example.com", + userType: UserTypePerson, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := policy.CheckTagOwners([]string{tc.tag}, &User{Name: tc.userName, UserType: tc.userType}) + if tc.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/internal/domain/principal.go b/internal/domain/principal.go new file mode 100644 index 0000000..5646661 --- /dev/null +++ b/internal/domain/principal.go @@ -0,0 +1,23 @@ +package domain + +type Principal struct { + SystemRole SystemRole + User *User + UserRole UserRole +} + +func (p Principal) IsSystemAdmin() bool { + return p.SystemRole.IsAdmin() +} + +func (p Principal) IsTailnetAdmin(tailnetID uint64) bool { + return p.User.TailnetID == tailnetID && p.UserRole.IsAdmin() +} + +func (p Principal) IsTailnetMember(tailnetID uint64) bool { + return p.User.TailnetID == tailnetID +} + +func (p Principal) UserMatches(userID uint64) bool { + return p.User.ID == userID +} diff --git a/internal/handlers/authentication.go b/internal/handlers/authentication.go index ab6d90f..bda9b22 100644 --- a/internal/handlers/authentication.go +++ b/internal/handlers/authentication.go @@ -246,6 +246,8 @@ func (h *AuthenticationHandlers) Error(c echo.Context) error { return c.Render(http.StatusForbidden, "invalidauthkey.html", nil) case "ua": return c.Render(http.StatusForbidden, "unauthorized.html", nil) + case "nto": + return c.Render(http.StatusForbidden, "notagowner.html", nil) } return c.Render(http.StatusOK, "error.html", nil) } @@ -382,6 +384,15 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi ephemeral = false } + if err := tailnet.ACLPolicy.CheckTagOwners(registrationRequest.Data.Hostinfo.RequestTags, user); err != nil { + registrationRequest.Authenticated = false + registrationRequest.Error = err.Error() + if err := h.repository.SaveRegistrationRequest(ctx, registrationRequest); err != nil { + return c.Redirect(http.StatusFound, "/a/error") + } + return c.Redirect(http.StatusFound, "/a/error?e=nto") + } + var m *domain.Machine m, err := h.repository.GetMachineByKey(ctx, tailnet.ID, machineKey) diff --git a/internal/handlers/registration.go b/internal/handlers/registration.go index 2900525..846973e 100644 --- a/internal/handlers/registration.go +++ b/internal/handlers/registration.go @@ -151,6 +151,11 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi tailnet := authKey.Tailnet user := authKey.User + if err := tailnet.ACLPolicy.CheckTagOwners(req.Hostinfo.RequestTags, &user); err != nil { + response := tailcfg.RegisterResponse{MachineAuthorized: false, Error: err.Error()} + return binder.WriteResponse(c, http.StatusOK, response) + } + var m *domain.Machine m, err = h.repository.GetMachineByKey(ctx, tailnet.ID, machineKey) @@ -158,7 +163,6 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, bi return err } - //var expiryDisabled bool now := time.Now().UTC() if m == nil { diff --git a/internal/service/auth_keys.go b/internal/service/auth_keys.go index 404e611..0924581 100644 --- a/internal/service/auth_keys.go +++ b/internal/service/auth_keys.go @@ -134,6 +134,16 @@ func (s *Service) CreateAuthKey(ctx context.Context, req *connect.Request[api.Cr return nil, connect.NewError(connect.CodeNotFound, errors.New("tailnet not found")) } + if principal.IsSystemAdmin() { + if err := tailnet.ACLPolicy.CheckTags(req.Msg.Tags); err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + } else { + if err := tailnet.ACLPolicy.CheckTagOwners(req.Msg.Tags, principal.User); err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + } + var expiresAt *time.Time var expiresAtPb *timestamppb.Timestamp diff --git a/internal/service/interceptors.go b/internal/service/interceptors.go index f468fa4..e43f332 100644 --- a/internal/service/interceptors.go +++ b/internal/service/interceptors.go @@ -18,34 +18,12 @@ const ( principalKey = "principalKay" ) -type Principal struct { - SystemRole domain.SystemRole - User *domain.User - UserRole domain.UserRole -} - -func (p Principal) IsSystemAdmin() bool { - return p.SystemRole.IsAdmin() -} - -func (p Principal) IsTailnetAdmin(tailnetID uint64) bool { - return p.User.TailnetID == tailnetID && p.UserRole.IsAdmin() -} - -func (p Principal) IsTailnetMember(tailnetID uint64) bool { - return p.User.TailnetID == tailnetID -} - -func (p Principal) UserMatches(userID uint64) bool { - return p.User.ID == userID -} - -func CurrentPrincipal(ctx context.Context) Principal { +func CurrentPrincipal(ctx context.Context) domain.Principal { p := ctx.Value(principalKey) if p == nil { - return Principal{SystemRole: domain.SystemRoleNone, UserRole: domain.UserRoleNone} + return domain.Principal{SystemRole: domain.SystemRoleNone, UserRole: domain.UserRoleNone} } - return p.(Principal) + return p.(domain.Principal) } func AuthenticationInterceptor(systemAdminKey *key.ServerPrivate, repository domain.Repository) connect.UnaryInterceptorFunc { @@ -69,7 +47,7 @@ func AuthenticationInterceptor(systemAdminKey *key.ServerPrivate, repository dom } } -func exchangeToken(ctx context.Context, systemAdminKey *key.ServerPrivate, repository domain.Repository, value string) *Principal { +func exchangeToken(ctx context.Context, systemAdminKey *key.ServerPrivate, repository domain.Repository, value string) *domain.Principal { if len(value) == 0 { return nil } @@ -77,7 +55,7 @@ func exchangeToken(ctx context.Context, systemAdminKey *key.ServerPrivate, repos if systemAdminKey != nil && token.IsSystemAdminToken(value) { _, err := token.ParseSystemAdminToken(*systemAdminKey, value) if err == nil { - return &Principal{SystemRole: domain.SystemRoleAdmin} + return &domain.Principal{SystemRole: domain.SystemRoleAdmin} } } @@ -87,12 +65,12 @@ func exchangeToken(ctx context.Context, systemAdminKey *key.ServerPrivate, repos tailnet := apiKey.Tailnet role := tailnet.IAMPolicy.GetRole(user) - return &Principal{User: &apiKey.User, SystemRole: domain.SystemRoleNone, UserRole: role} + return &domain.Principal{User: &apiKey.User, SystemRole: domain.SystemRoleNone, UserRole: role} } systemApiKey, err := repository.LoadSystemApiKey(ctx, value) if err == nil && systemApiKey != nil { - return &Principal{SystemRole: domain.SystemRoleAdmin} + return &domain.Principal{SystemRole: domain.SystemRoleAdmin} } return nil diff --git a/internal/templates/notagowner.html b/internal/templates/notagowner.html new file mode 100644 index 0000000..f2834ec --- /dev/null +++ b/internal/templates/notagowner.html @@ -0,0 +1,63 @@ + + + + + + + ionscale + + +
+
+

Authentication successful

+ but you're not a valid tag owner for the requested tags +
+
+ + \ No newline at end of file diff --git a/pkg/gen/ionscale/v1/acl.pb.go b/pkg/gen/ionscale/v1/acl.pb.go index 2398472..7495ed9 100644 --- a/pkg/gen/ionscale/v1/acl.pb.go +++ b/pkg/gen/ionscale/v1/acl.pb.go @@ -213,9 +213,10 @@ type ACLPolicy struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Hosts map[string]string `protobuf:"bytes,1,rep,name=hosts,proto3" json:"hosts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Groups map[string]*structpb.ListValue `protobuf:"bytes,2,rep,name=groups,proto3" json:"groups,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Acls []*ACL `protobuf:"bytes,3,rep,name=acls,proto3" json:"acls,omitempty"` + Hosts map[string]string `protobuf:"bytes,1,rep,name=hosts,proto3" json:"hosts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Groups map[string]*structpb.ListValue `protobuf:"bytes,2,rep,name=groups,proto3" json:"groups,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Acls []*ACL `protobuf:"bytes,3,rep,name=acls,proto3" json:"acls,omitempty"` + TagOwners map[string]*structpb.ListValue `protobuf:"bytes,4,rep,name=tag_owners,json=tagOwners,proto3" json:"tag_owners,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *ACLPolicy) Reset() { @@ -271,6 +272,13 @@ func (x *ACLPolicy) GetAcls() []*ACL { return nil } +func (x *ACLPolicy) GetTagOwners() map[string]*structpb.ListValue { + if x != nil { + return x.TagOwners + } + return nil +} + type ACL struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -358,7 +366,7 @@ var file_ionscale_v1_acl_proto_rawDesc = []byte{ 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb7, 0x02, 0x0a, 0x09, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd7, 0x03, 0x0a, 0x09, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, @@ -368,12 +376,22 @@ var file_ionscale_v1_acl_proto_rawDesc = []byte{ 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x24, 0x0a, 0x04, 0x61, 0x63, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x52, 0x04, 0x61, 0x63, 0x6c, 0x73, 0x1a, 0x38, - 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x55, 0x0a, 0x0b, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x43, 0x4c, 0x52, 0x04, 0x61, 0x63, 0x6c, 0x73, 0x12, 0x44, + 0x0a, 0x0a, 0x74, 0x61, 0x67, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x69, 0x6f, 0x6e, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x43, 0x4c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x54, 0x61, 0x67, 0x4f, 0x77, + 0x6e, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x74, 0x61, 0x67, 0x4f, 0x77, + 0x6e, 0x65, 0x72, 0x73, 0x1a, 0x38, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x55, + 0x0a, 0x0b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x58, 0x0a, 0x0e, 0x54, 0x61, 0x67, 0x4f, 0x77, 0x6e, 0x65, + 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, @@ -401,7 +419,7 @@ func file_ionscale_v1_acl_proto_rawDescGZIP() []byte { return file_ionscale_v1_acl_proto_rawDescData } -var file_ionscale_v1_acl_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_ionscale_v1_acl_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_ionscale_v1_acl_proto_goTypes = []interface{}{ (*GetACLPolicyRequest)(nil), // 0: ionscale.v1.GetACLPolicyRequest (*GetACLPolicyResponse)(nil), // 1: ionscale.v1.GetACLPolicyResponse @@ -411,7 +429,8 @@ var file_ionscale_v1_acl_proto_goTypes = []interface{}{ (*ACL)(nil), // 5: ionscale.v1.ACL nil, // 6: ionscale.v1.ACLPolicy.HostsEntry nil, // 7: ionscale.v1.ACLPolicy.GroupsEntry - (*structpb.ListValue)(nil), // 8: google.protobuf.ListValue + nil, // 8: ionscale.v1.ACLPolicy.TagOwnersEntry + (*structpb.ListValue)(nil), // 9: google.protobuf.ListValue } var file_ionscale_v1_acl_proto_depIdxs = []int32{ 4, // 0: ionscale.v1.GetACLPolicyResponse.policy:type_name -> ionscale.v1.ACLPolicy @@ -419,12 +438,14 @@ var file_ionscale_v1_acl_proto_depIdxs = []int32{ 6, // 2: ionscale.v1.ACLPolicy.hosts:type_name -> ionscale.v1.ACLPolicy.HostsEntry 7, // 3: ionscale.v1.ACLPolicy.groups:type_name -> ionscale.v1.ACLPolicy.GroupsEntry 5, // 4: ionscale.v1.ACLPolicy.acls:type_name -> ionscale.v1.ACL - 8, // 5: ionscale.v1.ACLPolicy.GroupsEntry.value:type_name -> google.protobuf.ListValue - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 8, // 5: ionscale.v1.ACLPolicy.tag_owners:type_name -> ionscale.v1.ACLPolicy.TagOwnersEntry + 9, // 6: ionscale.v1.ACLPolicy.GroupsEntry.value:type_name -> google.protobuf.ListValue + 9, // 7: ionscale.v1.ACLPolicy.TagOwnersEntry.value:type_name -> google.protobuf.ListValue + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_ionscale_v1_acl_proto_init() } @@ -513,7 +534,7 @@ func file_ionscale_v1_acl_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_ionscale_v1_acl_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/ionscale/v1/acl.proto b/proto/ionscale/v1/acl.proto index 27ac5f9..37deb0a 100644 --- a/proto/ionscale/v1/acl.proto +++ b/proto/ionscale/v1/acl.proto @@ -27,6 +27,7 @@ message ACLPolicy { map hosts = 1; map groups = 2; repeated ACL acls = 3; + map tag_owners = 4; } message ACL {