diff --git a/deb/uploaders.go b/deb/uploaders.go new file mode 100644 index 00000000..c6c548b3 --- /dev/null +++ b/deb/uploaders.go @@ -0,0 +1,74 @@ +package deb + +import ( + "github.com/smira/aptly/utils" +) + +// UploadersRule is single rule of format: what packages can group or key upload +type UploadersRule struct { + Condition string `json:"condition"` + Allow []string `json:"allow"` + Deny []string `json:"deny"` + CompiledCondition PackageQuery `json:"-"` +} + +// Uploaders is configuration of restrictions for .changes file importing +type Uploaders struct { + Groups map[string][]string `json:"groups"` + Rules []UploadersRule `json:"rules"` +} + +func (u *Uploaders) expandGroupsInternal(items []string, trail []string) []string { + result := []string{} + + for _, item := range items { + // stop infinite recursion + if utils.StrSliceHasItem(trail, item) { + continue + } + + group, ok := u.Groups[item] + if !ok { + result = append(result, item) + } else { + newTrail := append([]string(nil), trail...) + result = append(result, u.expandGroupsInternal(group, append(newTrail, item))...) + } + } + + return result +} + +// ExpandGroups expands list of keys/groups into list of keys +func (u *Uploaders) ExpandGroups(items []string) []string { + result := u.expandGroupsInternal(items, []string{}) + + return utils.StrSliceDeduplicate(result) +} + +// IsAllowed checks whether listed keys are allowed to upload given .changes file +func (u *Uploaders) IsAllowed(keys []utils.GpgKey, changes *Changes) bool { + for _, rule := range u.Rules { + if rule.CompiledCondition.Matches(changes) { + deny := u.ExpandGroups(rule.Deny) + for _, key := range keys { + for _, item := range deny { + if item == "*" || key.Matches(utils.GpgKey(item)) { + return false + } + } + } + + allow := u.ExpandGroups(rule.Allow) + for _, key := range keys { + for _, item := range allow { + if item == "*" || key.Matches(utils.GpgKey(item)) { + return true + } + } + } + } + } + + return false +} diff --git a/deb/uploaders_test.go b/deb/uploaders_test.go new file mode 100644 index 00000000..5f90706d --- /dev/null +++ b/deb/uploaders_test.go @@ -0,0 +1,79 @@ +package deb + +import ( + "github.com/smira/aptly/utils" + . "gopkg.in/check.v1" +) + +type UploadersSuite struct { +} + +var _ = Suite(&UploadersSuite{}) + +func (s *UploadersSuite) TestExpandGroups(c *C) { + u := &Uploaders{ + Groups: map[string][]string{ + "group1": {"key1", "group2"}, + "group2": {"key1", "key2", "key3", "group3"}, + "group3": {}, + "group4": {"key1", "group5"}, + "group6": {"key1", "group8"}, + "group7": {"key2", "group6"}, + "group8": {"group7"}, + }, + } + + c.Check(u.ExpandGroups([]string{"group1"}), DeepEquals, []string{"key1", "key2", "key3"}) + c.Check(u.ExpandGroups([]string{"group2"}), DeepEquals, []string{"key1", "key2", "key3"}) + c.Check(u.ExpandGroups([]string{"group3"}), DeepEquals, []string{}) + c.Check(u.ExpandGroups([]string{"group4"}), DeepEquals, []string{"key1", "group5"}) + c.Check(u.ExpandGroups([]string{"group6"}), DeepEquals, []string{"key1", "key2"}) + c.Check(u.ExpandGroups([]string{"group7"}), DeepEquals, []string{"key2", "key1"}) + c.Check(u.ExpandGroups([]string{"group8"}), DeepEquals, []string{"key2", "key1"}) +} + +func (s *UploadersSuite) TestIsAllowed(c *C) { + u := &Uploaders{ + Groups: map[string][]string{ + "group1": {"37E1C17570096AD1", "EC4B033C70096AD1"}, + }, + Rules: []UploadersRule{ + { + CompiledCondition: &FieldQuery{Field: "Source", Relation: VersionEqual, Value: "calamares"}, + Allow: []string{"*"}, + }, + { + CompiledCondition: &FieldQuery{Field: "Source", Relation: VersionEqual, Value: "never-calamares"}, + Deny: []string{"*"}, + }, + { + CompiledCondition: &FieldQuery{Field: "Source", Relation: VersionEqual, Value: "some-calamares"}, + Allow: []string{"group1", "12345678"}, + }, + { + CompiledCondition: &FieldQuery{Field: "Source", Relation: VersionEqual, Value: "some-calamares"}, + Deny: []string{"45678901", "12345678"}, + }, + }, + } + + // no keys - not allowed + c.Check(u.IsAllowed([]utils.GpgKey{}, &Changes{Stanza: Stanza{"Source": "calamares"}}), Equals, false) + + // no rule - not allowed + c.Check(u.IsAllowed([]utils.GpgKey{"37E1C17570096AD1", "EC4B033C70096AD1"}, &Changes{Stanza: Stanza{"Source": "unknown-calamares"}}), Equals, false) + + // first rule: allow anyone do stuff with calamares + c.Check(u.IsAllowed([]utils.GpgKey{"ABCD1234", "1234ABCD"}, &Changes{Stanza: Stanza{"Source": "calamares"}}), Equals, true) + + // second rule: nobody is allowed to do stuff with never-calamares + c.Check(u.IsAllowed([]utils.GpgKey{"ABCD1234", "1234ABCD"}, &Changes{Stanza: Stanza{"Source": "never-calamares"}}), Equals, false) + + // third rule: anyone from the group or explicit key + c.Check(u.IsAllowed([]utils.GpgKey{"45678901", "12345678"}, &Changes{Stanza: Stanza{"Source": "some-calamares"}}), Equals, true) + c.Check(u.IsAllowed([]utils.GpgKey{"37E1C17570096AD1"}, &Changes{Stanza: Stanza{"Source": "some-calamares"}}), Equals, true) + c.Check(u.IsAllowed([]utils.GpgKey{"70096AD1"}, &Changes{Stanza: Stanza{"Source": "some-calamares"}}), Equals, true) + + // fourth rule: some are not allowed + c.Check(u.IsAllowed([]utils.GpgKey{"ABCD1234", "45678901"}, &Changes{Stanza: Stanza{"Source": "some-calamares"}}), Equals, false) +}