diff --git a/hscontrol/policy/policy_test.go b/hscontrol/policy/policy_test.go index 87142dd9..da212605 100644 --- a/hscontrol/policy/policy_test.go +++ b/hscontrol/policy/policy_test.go @@ -1092,6 +1092,15 @@ func TestSSHPolicyRules(t *testing.T) { Tags: []string{"tag:client"}, } + // Create a tagged server node for valid SSH patterns + nodeTaggedServer := types.Node{ + Hostname: "tagged-server", + IPv4: ap("100.64.0.5"), + UserID: ptr.To(uint(1)), + User: ptr.To(users[0]), + Tags: []string{"tag:server"}, + } + tests := []struct { name string targetNode types.Node @@ -1102,10 +1111,13 @@ func TestSSHPolicyRules(t *testing.T) { errorMessage string }{ { - name: "group-to-user", - targetNode: nodeUser1, + name: "group-to-tag", + targetNode: nodeTaggedServer, peers: types.Nodes{&nodeUser2}, policy: `{ + "tagOwners": { + "tag:server": ["user1@"] + }, "groups": { "group:admins": ["user2@"] }, @@ -1113,7 +1125,7 @@ func TestSSHPolicyRules(t *testing.T) { { "action": "accept", "src": ["group:admins"], - "dst": ["user1@"], + "dst": ["tag:server"], "users": ["autogroup:nonroot"] } ] @@ -1138,18 +1150,21 @@ func TestSSHPolicyRules(t *testing.T) { }, { name: "check-period-specified", - targetNode: nodeUser1, - peers: types.Nodes{&taggedClient}, + targetNode: taggedClient, + peers: types.Nodes{&nodeUser2}, policy: `{ "tagOwners": { - "tag:client": ["user1@"], + "tag:client": ["user1@"] + }, + "groups": { + "group:admins": ["user2@"] }, "ssh": [ { "action": "check", "checkPeriod": "24h", - "src": ["tag:client"], - "dst": ["user1@"], + "src": ["group:admins"], + "dst": ["tag:client"], "users": ["autogroup:nonroot"] } ] @@ -1157,7 +1172,7 @@ func TestSSHPolicyRules(t *testing.T) { wantSSH: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{ { Principals: []*tailcfg.SSHPrincipal{ - {NodeIP: "100.64.0.4"}, + {NodeIP: "100.64.0.2"}, }, SSHUsers: map[string]string{ "*": "=", @@ -1176,16 +1191,19 @@ func TestSSHPolicyRules(t *testing.T) { { name: "no-matching-rules", targetNode: nodeUser2, - peers: types.Nodes{&nodeUser1}, + peers: types.Nodes{&nodeUser1, &nodeTaggedServer}, policy: `{ "tagOwners": { - "tag:client": ["user1@"], + "tag:server": ["user1@"] }, + "groups": { + "group:admins": ["user1@"] + }, "ssh": [ { "action": "accept", - "src": ["tag:client"], - "dst": ["user1@"], + "src": ["group:admins"], + "dst": ["tag:server"], "users": ["autogroup:nonroot"] } ] @@ -1194,14 +1212,20 @@ func TestSSHPolicyRules(t *testing.T) { }, { name: "invalid-action", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, peers: types.Nodes{&nodeUser2}, policy: `{ + "tagOwners": { + "tag:server": ["user1@"] + }, + "groups": { + "group:admins": ["user2@"] + }, "ssh": [ { "action": "invalid", "src": ["group:admins"], - "dst": ["user1@"], + "dst": ["tag:server"], "users": ["autogroup:nonroot"] } ] @@ -1211,15 +1235,21 @@ func TestSSHPolicyRules(t *testing.T) { }, { name: "invalid-check-period", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, peers: types.Nodes{&nodeUser2}, policy: `{ + "tagOwners": { + "tag:server": ["user1@"] + }, + "groups": { + "group:admins": ["user2@"] + }, "ssh": [ { "action": "check", "checkPeriod": "invalid", "src": ["group:admins"], - "dst": ["user1@"], + "dst": ["tag:server"], "users": ["autogroup:nonroot"] } ] @@ -1229,26 +1259,12 @@ func TestSSHPolicyRules(t *testing.T) { }, { name: "unsupported-autogroup", - targetNode: nodeUser1, - peers: types.Nodes{&taggedClient}, - policy: `{ - "ssh": [ - { - "action": "accept", - "src": ["tag:client"], - "dst": ["user1@"], - "users": ["autogroup:invalid"] - } - ] - }`, - expectErr: true, - errorMessage: "autogroup \"autogroup:invalid\" is not supported", - }, - { - name: "autogroup-nonroot-should-use-wildcard-with-root-excluded", - targetNode: nodeUser1, + targetNode: taggedClient, peers: types.Nodes{&nodeUser2}, policy: `{ + "tagOwners": { + "tag:client": ["user1@"] + }, "groups": { "group:admins": ["user2@"] }, @@ -1256,7 +1272,30 @@ func TestSSHPolicyRules(t *testing.T) { { "action": "accept", "src": ["group:admins"], - "dst": ["user1@"], + "dst": ["tag:client"], + "users": ["autogroup:invalid"] + } + ] + }`, + expectErr: true, + errorMessage: "autogroup \"autogroup:invalid\" is not supported", + }, + { + name: "autogroup-nonroot-should-use-wildcard-with-root-excluded", + targetNode: nodeTaggedServer, + peers: types.Nodes{&nodeUser2}, + policy: `{ + "tagOwners": { + "tag:server": ["user1@"] + }, + "groups": { + "group:admins": ["user2@"] + }, + "ssh": [ + { + "action": "accept", + "src": ["group:admins"], + "dst": ["tag:server"], "users": ["autogroup:nonroot"] } ] @@ -1282,9 +1321,12 @@ func TestSSHPolicyRules(t *testing.T) { }, { name: "autogroup-nonroot-plus-root-should-use-wildcard-with-root-mapped", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, peers: types.Nodes{&nodeUser2}, policy: `{ + "tagOwners": { + "tag:server": ["user1@"] + }, "groups": { "group:admins": ["user2@"] }, @@ -1292,7 +1334,7 @@ func TestSSHPolicyRules(t *testing.T) { { "action": "accept", "src": ["group:admins"], - "dst": ["user1@"], + "dst": ["tag:server"], "users": ["autogroup:nonroot", "root"] } ] @@ -1318,9 +1360,12 @@ func TestSSHPolicyRules(t *testing.T) { }, { name: "specific-users-should-map-to-themselves-not-equals", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, peers: types.Nodes{&nodeUser2}, policy: `{ + "tagOwners": { + "tag:server": ["user1@"] + }, "groups": { "group:admins": ["user2@"] }, @@ -1328,7 +1373,7 @@ func TestSSHPolicyRules(t *testing.T) { { "action": "accept", "src": ["group:admins"], - "dst": ["user1@"], + "dst": ["tag:server"], "users": ["ubuntu", "root"] } ] diff --git a/hscontrol/policy/v2/filter_test.go b/hscontrol/policy/v2/filter_test.go index 46f544c9..0df1e147 100644 --- a/hscontrol/policy/v2/filter_test.go +++ b/hscontrol/policy/v2/filter_test.go @@ -406,21 +406,33 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { {Name: "user2", Model: gorm.Model{ID: 2}}, } - // Create test nodes - nodeUser1 := types.Node{ - Hostname: "user1-device", + // Create test nodes - use tagged nodes as SSH destinations + // and untagged nodes as SSH sources (since group->username destinations + // are not allowed per Tailscale security model, but groups can SSH to tags) + nodeTaggedServer := types.Node{ + Hostname: "tagged-server", IPv4: createAddr("100.64.0.1"), UserID: ptr.To(users[0].ID), User: ptr.To(users[0]), + Tags: []string{"tag:server"}, } - nodeUser2 := types.Node{ - Hostname: "user2-device", + nodeTaggedDB := types.Node{ + Hostname: "tagged-db", IPv4: createAddr("100.64.0.2"), UserID: ptr.To(users[1].ID), User: ptr.To(users[1]), + Tags: []string{"tag:database"}, + } + // Add untagged node for user2 - this will be the SSH source + // (group:admins contains user2, so user2's untagged node provides the source IPs) + nodeUser2Untagged := types.Node{ + Hostname: "user2-device", + IPv4: createAddr("100.64.0.3"), + UserID: ptr.To(users[1].ID), + User: ptr.To(users[1]), } - nodes := types.Nodes{&nodeUser1, &nodeUser2} + nodes := types.Nodes{&nodeTaggedServer, &nodeTaggedDB, &nodeUser2Untagged} tests := []struct { name string @@ -431,8 +443,11 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { }{ { name: "specific user mapping", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, policy: &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("user1@")}, + }, Groups: Groups{ Group("group:admins"): []Username{Username("user2@")}, }, @@ -440,7 +455,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { { Action: "accept", Sources: SSHSrcAliases{gp("group:admins")}, - Destinations: SSHDstAliases{up("user1@")}, + Destinations: SSHDstAliases{tp("tag:server")}, Users: []SSHUser{"ssh-it-user"}, }, }, @@ -451,8 +466,11 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { }, { name: "multiple specific users", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, policy: &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("user1@")}, + }, Groups: Groups{ Group("group:admins"): []Username{Username("user2@")}, }, @@ -460,7 +478,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { { Action: "accept", Sources: SSHSrcAliases{gp("group:admins")}, - Destinations: SSHDstAliases{up("user1@")}, + Destinations: SSHDstAliases{tp("tag:server")}, Users: []SSHUser{"ubuntu", "admin", "deploy"}, }, }, @@ -473,8 +491,11 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { }, { name: "autogroup:nonroot only", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, policy: &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("user1@")}, + }, Groups: Groups{ Group("group:admins"): []Username{Username("user2@")}, }, @@ -482,7 +503,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { { Action: "accept", Sources: SSHSrcAliases{gp("group:admins")}, - Destinations: SSHDstAliases{up("user1@")}, + Destinations: SSHDstAliases{tp("tag:server")}, Users: []SSHUser{SSHUser(AutoGroupNonRoot)}, }, }, @@ -494,8 +515,11 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { }, { name: "root only", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, policy: &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("user1@")}, + }, Groups: Groups{ Group("group:admins"): []Username{Username("user2@")}, }, @@ -503,7 +527,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { { Action: "accept", Sources: SSHSrcAliases{gp("group:admins")}, - Destinations: SSHDstAliases{up("user1@")}, + Destinations: SSHDstAliases{tp("tag:server")}, Users: []SSHUser{"root"}, }, }, @@ -514,8 +538,11 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { }, { name: "autogroup:nonroot plus root", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, policy: &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("user1@")}, + }, Groups: Groups{ Group("group:admins"): []Username{Username("user2@")}, }, @@ -523,7 +550,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { { Action: "accept", Sources: SSHSrcAliases{gp("group:admins")}, - Destinations: SSHDstAliases{up("user1@")}, + Destinations: SSHDstAliases{tp("tag:server")}, Users: []SSHUser{SSHUser(AutoGroupNonRoot), "root"}, }, }, @@ -535,8 +562,11 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { }, { name: "mixed specific users and autogroups", - targetNode: nodeUser1, + targetNode: nodeTaggedServer, policy: &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("user1@")}, + }, Groups: Groups{ Group("group:admins"): []Username{Username("user2@")}, }, @@ -544,7 +574,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { { Action: "accept", Sources: SSHSrcAliases{gp("group:admins")}, - Destinations: SSHDstAliases{up("user1@")}, + Destinations: SSHDstAliases{tp("tag:server")}, Users: []SSHUser{SSHUser(AutoGroupNonRoot), "root", "ubuntu", "admin"}, }, }, @@ -558,8 +588,12 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { }, { name: "no matching destination", - targetNode: nodeUser2, // Target node2, but policy only allows user1 + targetNode: nodeTaggedDB, // Target tag:database, but policy only allows tag:server policy: &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("user1@")}, + Tag("tag:database"): Owners{up("user1@")}, + }, Groups: Groups{ Group("group:admins"): []Username{Username("user2@")}, }, @@ -567,7 +601,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { { Action: "accept", Sources: SSHSrcAliases{gp("group:admins")}, - Destinations: SSHDstAliases{up("user1@")}, // Only user1, not user2 + Destinations: SSHDstAliases{tp("tag:server")}, // Only tag:server, not tag:database Users: []SSHUser{"ssh-it-user"}, }, }, @@ -600,9 +634,9 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { rule := sshPolicy.Rules[0] assert.Equal(t, tt.wantSSHUsers, rule.SSHUsers, "SSH users mapping should match expected") - // Verify principals are set correctly (should contain user2's IP since that's the source) + // Verify principals are set correctly (should contain user2's untagged device IP since that's the source) require.Len(t, rule.Principals, 1) - assert.Equal(t, "100.64.0.2", rule.Principals[0].NodeIP) + assert.Equal(t, "100.64.0.3", rule.Principals[0].NodeIP) // Verify action is set correctly assert.True(t, rule.Action.Accept) @@ -619,11 +653,13 @@ func TestCompileSSHPolicy_CheckAction(t *testing.T) { {Name: "user2", Model: gorm.Model{ID: 2}}, } - nodeUser1 := types.Node{ - Hostname: "user1-device", + // Use tagged nodes for SSH user mapping tests + nodeTaggedServer := types.Node{ + Hostname: "tagged-server", IPv4: createAddr("100.64.0.1"), UserID: ptr.To(users[0].ID), User: ptr.To(users[0]), + Tags: []string{"tag:server"}, } nodeUser2 := types.Node{ Hostname: "user2-device", @@ -632,9 +668,12 @@ func TestCompileSSHPolicy_CheckAction(t *testing.T) { User: ptr.To(users[1]), } - nodes := types.Nodes{&nodeUser1, &nodeUser2} + nodes := types.Nodes{&nodeTaggedServer, &nodeUser2} policy := &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("user1@")}, + }, Groups: Groups{ Group("group:admins"): []Username{Username("user2@")}, }, @@ -643,7 +682,7 @@ func TestCompileSSHPolicy_CheckAction(t *testing.T) { Action: "check", CheckPeriod: model.Duration(24 * time.Hour), Sources: SSHSrcAliases{gp("group:admins")}, - Destinations: SSHDstAliases{up("user1@")}, + Destinations: SSHDstAliases{tp("tag:server")}, Users: []SSHUser{"ssh-it-user"}, }, }, @@ -652,7 +691,7 @@ func TestCompileSSHPolicy_CheckAction(t *testing.T) { err := policy.validate() require.NoError(t, err) - sshPolicy, err := policy.compileSSHPolicy(users, nodeUser1.View(), nodes.ViewSlice()) + sshPolicy, err := policy.compileSSHPolicy(users, nodeTaggedServer.View(), nodes.ViewSlice()) require.NoError(t, err) require.NotNil(t, sshPolicy) require.Len(t, sshPolicy.Rules, 1) @@ -697,16 +736,17 @@ func TestSSHIntegrationReproduction(t *testing.T) { nodes := types.Nodes{node1, node2} // Create a simple policy that reproduces the issue + // Updated to use autogroup:self instead of username destination (per Tailscale security model) policy := &Policy{ Groups: Groups{ - Group("group:integration-test"): []Username{Username("user1@")}, + Group("group:integration-test"): []Username{Username("user1@"), Username("user2@")}, }, SSHs: []SSH{ { Action: "accept", Sources: SSHSrcAliases{gp("group:integration-test")}, - Destinations: SSHDstAliases{up("user2@")}, // Target user2 - Users: []SSHUser{SSHUser("ssh-it-user")}, // This is the key - specific user + Destinations: SSHDstAliases{agp("autogroup:self")}, // Users can SSH to their own devices + Users: []SSHUser{SSHUser("ssh-it-user")}, // This is the key - specific user }, }, } @@ -715,7 +755,7 @@ func TestSSHIntegrationReproduction(t *testing.T) { err := policy.validate() require.NoError(t, err) - // Test SSH policy compilation for node2 (target) + // Test SSH policy compilation for node2 (owned by user2, who is in the group) sshPolicy, err := policy.compileSSHPolicy(users, node2.View(), nodes.ViewSlice()) require.NoError(t, err) require.NotNil(t, sshPolicy) diff --git a/hscontrol/policy/v2/types_test.go b/hscontrol/policy/v2/types_test.go index 664f76b7..542c9b2c 100644 --- a/hscontrol/policy/v2/types_test.go +++ b/hscontrol/policy/v2/types_test.go @@ -664,7 +664,8 @@ func TestUnmarshalPolicy(t *testing.T) { input: ` { "tagOwners": { - "tag:web": ["admin@example.com"] + "tag:web": ["admin@example.com"], + "tag:server": ["admin@example.com"] }, "ssh": [ { @@ -673,7 +674,7 @@ func TestUnmarshalPolicy(t *testing.T) { "tag:web" ], "dst": [ - "admin@example.com" + "tag:server" ], "users": ["*"] } @@ -682,7 +683,8 @@ func TestUnmarshalPolicy(t *testing.T) { `, want: &Policy{ TagOwners: TagOwners{ - Tag("tag:web"): Owners{ptr.To(Username("admin@example.com"))}, + Tag("tag:web"): Owners{ptr.To(Username("admin@example.com"))}, + Tag("tag:server"): Owners{ptr.To(Username("admin@example.com"))}, }, SSHs: []SSH{ { @@ -691,7 +693,7 @@ func TestUnmarshalPolicy(t *testing.T) { tp("tag:web"), }, Destinations: SSHDstAliases{ - ptr.To(Username("admin@example.com")), + tp("tag:server"), }, Users: []SSHUser{ SSHUser("*"), @@ -714,7 +716,7 @@ func TestUnmarshalPolicy(t *testing.T) { "group:admins" ], "dst": [ - "admin@example.com" + "autogroup:self" ], "users": ["root"], "checkPeriod": "24h" @@ -733,7 +735,7 @@ func TestUnmarshalPolicy(t *testing.T) { gp("group:admins"), }, Destinations: SSHDstAliases{ - ptr.To(Username("admin@example.com")), + agp("autogroup:self"), }, Users: []SSHUser{ SSHUser("root"), @@ -1521,6 +1523,249 @@ func TestUnmarshalPolicy(t *testing.T) { `, wantErr: `tag "tag:child" references undefined tag "tag:nonexistent"`, }, + // SSH source/destination validation tests (#3009, #3010) + { + name: "ssh-tag-to-user-rejected", + input: ` +{ + "tagOwners": {"tag:server": ["admin@"]}, + "ssh": [{ + "action": "accept", + "src": ["tag:server"], + "dst": ["admin@"], + "users": ["autogroup:nonroot"] + }] +} +`, + wantErr: "tags in SSH source cannot access user-owned devices", + }, + { + name: "ssh-autogroup-tagged-to-user-rejected", + input: ` +{ + "ssh": [{ + "action": "accept", + "src": ["autogroup:tagged"], + "dst": ["admin@"], + "users": ["autogroup:nonroot"] + }] +} +`, + wantErr: "tags in SSH source cannot access user-owned devices", + }, + { + name: "ssh-tag-to-autogroup-self-rejected", + input: ` +{ + "tagOwners": {"tag:server": ["admin@"]}, + "ssh": [{ + "action": "accept", + "src": ["tag:server"], + "dst": ["autogroup:self"], + "users": ["autogroup:nonroot"] + }] +} +`, + wantErr: "autogroup:self destination requires source to contain only users or groups", + }, + { + name: "ssh-group-to-user-rejected", + input: ` +{ + "groups": {"group:admins": ["admin@", "user1@"]}, + "ssh": [{ + "action": "accept", + "src": ["group:admins"], + "dst": ["admin@"], + "users": ["autogroup:nonroot"] + }] +} +`, + wantErr: `user destination requires source to contain only that same user "admin@"`, + }, + { + name: "ssh-same-user-to-user-allowed", + input: ` +{ + "ssh": [{ + "action": "accept", + "src": ["admin@"], + "dst": ["admin@"], + "users": ["autogroup:nonroot"] + }] +} +`, + want: &Policy{ + SSHs: []SSH{ + { + Action: "accept", + Sources: SSHSrcAliases{up("admin@")}, + Destinations: SSHDstAliases{up("admin@")}, + Users: []SSHUser{SSHUser(AutoGroupNonRoot)}, + }, + }, + }, + }, + { + name: "ssh-group-to-autogroup-self-allowed", + input: ` +{ + "groups": {"group:admins": ["admin@", "user1@"]}, + "ssh": [{ + "action": "accept", + "src": ["group:admins"], + "dst": ["autogroup:self"], + "users": ["autogroup:nonroot"] + }] +} +`, + want: &Policy{ + Groups: Groups{ + Group("group:admins"): []Username{Username("admin@"), Username("user1@")}, + }, + SSHs: []SSH{ + { + Action: "accept", + Sources: SSHSrcAliases{gp("group:admins")}, + Destinations: SSHDstAliases{agp("autogroup:self")}, + Users: []SSHUser{SSHUser(AutoGroupNonRoot)}, + }, + }, + }, + }, + { + name: "ssh-autogroup-tagged-to-autogroup-member-rejected", + input: ` +{ + "ssh": [{ + "action": "accept", + "src": ["autogroup:tagged"], + "dst": ["autogroup:member"], + "users": ["autogroup:nonroot"] + }] +} +`, + wantErr: "tags in SSH source cannot access autogroup:member", + }, + { + name: "ssh-autogroup-tagged-to-autogroup-tagged-allowed", + input: ` +{ + "ssh": [{ + "action": "accept", + "src": ["autogroup:tagged"], + "dst": ["autogroup:tagged"], + "users": ["autogroup:nonroot"] + }] +} +`, + want: &Policy{ + SSHs: []SSH{ + { + Action: "accept", + Sources: SSHSrcAliases{agp("autogroup:tagged")}, + Destinations: SSHDstAliases{agp("autogroup:tagged")}, + Users: []SSHUser{SSHUser(AutoGroupNonRoot)}, + }, + }, + }, + }, + { + name: "ssh-wildcard-destination-rejected", + input: ` +{ + "groups": {"group:admins": ["admin@"]}, + "ssh": [{ + "action": "accept", + "src": ["group:admins"], + "dst": ["*"], + "users": ["autogroup:nonroot"] + }] +} +`, + wantErr: "wildcard (*) is not supported as SSH destination", + }, + { + name: "ssh-group-to-tag-allowed", + input: ` +{ + "tagOwners": {"tag:server": ["admin@"]}, + "groups": {"group:admins": ["admin@"]}, + "ssh": [{ + "action": "accept", + "src": ["group:admins"], + "dst": ["tag:server"], + "users": ["autogroup:nonroot"] + }] +} +`, + want: &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("admin@")}, + }, + Groups: Groups{ + Group("group:admins"): []Username{Username("admin@")}, + }, + SSHs: []SSH{ + { + Action: "accept", + Sources: SSHSrcAliases{gp("group:admins")}, + Destinations: SSHDstAliases{tp("tag:server")}, + Users: []SSHUser{SSHUser(AutoGroupNonRoot)}, + }, + }, + }, + }, + { + name: "ssh-user-to-tag-allowed", + input: ` +{ + "tagOwners": {"tag:server": ["admin@"]}, + "ssh": [{ + "action": "accept", + "src": ["admin@"], + "dst": ["tag:server"], + "users": ["autogroup:nonroot"] + }] +} +`, + want: &Policy{ + TagOwners: TagOwners{ + Tag("tag:server"): Owners{up("admin@")}, + }, + SSHs: []SSH{ + { + Action: "accept", + Sources: SSHSrcAliases{up("admin@")}, + Destinations: SSHDstAliases{tp("tag:server")}, + Users: []SSHUser{SSHUser(AutoGroupNonRoot)}, + }, + }, + }, + }, + { + name: "ssh-autogroup-member-to-autogroup-tagged-allowed", + input: ` +{ + "ssh": [{ + "action": "accept", + "src": ["autogroup:member"], + "dst": ["autogroup:tagged"], + "users": ["autogroup:nonroot"] + }] +} +`, + want: &Policy{ + SSHs: []SSH{ + { + Action: "accept", + Sources: SSHSrcAliases{agp("autogroup:member")}, + Destinations: SSHDstAliases{agp("autogroup:tagged")}, + Users: []SSHUser{SSHUser(AutoGroupNonRoot)}, + }, + }, + }, + }, } cmps := append(util.Comparers,